From 2add05557dcf6ad14d4125c67ecb753c587288ad Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 20 Sep 2023 19:20:19 +0300 Subject: [PATCH 001/434] add link of traffic-oriented public peers --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e8eefd7..c1dcba8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ Open trackers defined in [trackers.json](https://github.com/YGGverse/YGGtracker/ * Trackers not in list will be cropped by the application filter * Feel free to PR new yggdrasil tracker! +#### Public peers + +Traffic-oriented public peers for Yggdrasil defined in [peers.json](https://github.com/YGGverse/YGGtracker/blob/main/src/config/peers.json) + #### Requirements ``` From 34ae82532fbc6404a21bdb030af610f22ff98536 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 21 Sep 2023 01:19:56 +0300 Subject: [PATCH 002/434] draft v.2 database model #14 --- .gitignore | 3 ++- database/yggtracker.mwb | Bin 30568 -> 41069 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9a6ca43..6d2670d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ /vendor/ -/database/yggtracker.mwb.bak +/database/* +!/database/yggtracker.mwb /src/public/api/*.json diff --git a/database/yggtracker.mwb b/database/yggtracker.mwb index 4ca798049c810c44c42d3bce36ee6dab95789627..be8a9881458fdebf17b310420f924e03b168c368 100644 GIT binary patch literal 41069 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 From 3507abc35f412361e5771e4d920e30653108cba2 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 21 Sep 2023 15:03:10 +0300 Subject: [PATCH 003/434] update nodes description --- src/config/nodes.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/nodes.json b/src/config/nodes.json index 07cb99a..50a40d6 100644 --- a/src/config/nodes.json +++ b/src/config/nodes.json @@ -1,11 +1,11 @@ [ { - "description":"YGGtracker instance #1 with main branch updates", + "description":"YGGtracker instance #1 running latest stable release", "url":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker", "manifest":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/api/manifest.json" }, { - "description":"YGGtracker instance #2 with main branch updates", + "description":"YGGtracker instance #2 running latest stable release", "url":"http://[200:e6fd:bb3c:b354:cd3a:f939:753e:cd72]/yggtracker", "manifest":"http://[200:e6fd:bb3c:b354:cd3a:f939:753e:cd72]/yggtracker/api/manifest.json" } From fe2a7a575f71ac9963afb6ffaa8d4113b742e5f6 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 21 Sep 2023 15:22:02 +0300 Subject: [PATCH 004/434] add API version check #13, #14 --- example/environment/env.example.php | 2 -- src/config/bootstrap.php | 4 ++++ src/crontab/export/feed.php | 4 ++-- src/crontab/import/feed.php | 13 +++++++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/example/environment/env.example.php b/example/environment/env.example.php index 134afc5..0a2d22f 100644 --- a/example/environment/env.example.php +++ b/example/environment/env.example.php @@ -165,8 +165,6 @@ define('NODE_RULE_SUBJECT', 'Common'); define('NODE_RULE_LANGUAGES', 'All'); // API -define('API_VERSION', '1.0.0'); - define('API_USER_AGENT', WEBSITE_NAME); /// Export diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index 906adf0..70679bd 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -3,6 +3,10 @@ // PHP declare(strict_types=1); +// Application +define('APP_VERSION', '2.0.0'); +define('API_VERSION', APP_VERSION); + // Init environment if (!file_exists(__DIR__ . '/.env')) { diff --git a/src/crontab/export/feed.php b/src/crontab/export/feed.php index f895dd2..be41107 100644 --- a/src/crontab/export/feed.php +++ b/src/crontab/export/feed.php @@ -58,7 +58,8 @@ try // Manifest $manifest = [ - 'updated' => time(), + 'updated' => time(), + 'version' => (string) API_VERSION, 'settings' => (object) [ @@ -119,7 +120,6 @@ try 'MAGNET_STOP_WORDS_SIMILAR' => (object) MAGNET_STOP_WORDS_SIMILAR, - 'API_VERSION' => (string) API_VERSION, 'API_USER_AGENT' => (string) API_USER_AGENT, 'API_EXPORT_ENABLED' => (bool) API_EXPORT_ENABLED, diff --git a/src/crontab/import/feed.php b/src/crontab/import/feed.php index a1c657e..0cefd3b 100644 --- a/src/crontab/import/feed.php +++ b/src/crontab/import/feed.php @@ -135,6 +135,19 @@ try continue; } + if (empty($manifest->version) || $manifest->version !== API_VERSION) + { + array_push( + $debug['dump'], + sprintf( + _('Manifest API not compatible with local version "%s"'), + API_VERSION + ) + ); + + continue; + } + if (empty($manifest->export)) { array_push( From 67921a0d3e8e4c4c6181cdf2ec6bbb56f9d31675 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 21 Sep 2023 15:37:10 +0300 Subject: [PATCH 005/434] create separated search page #14 --- src/public/search.php | 436 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 436 insertions(+) create mode 100644 src/public/search.php diff --git a/src/public/search.php b/src/public/search.php new file mode 100644 index 0000000..ea1590c --- /dev/null +++ b/src/public/search.php @@ -0,0 +1,436 @@ + false, + 'page' => 1, +]; + +// Prepare request +$request->query = isset($_GET['query']) ? urldecode((string) $_GET['query']) : ''; +$request->page = isset($_GET['page']) && $_GET['page'] > 0 ? (int) $_GET['page'] : 1; + +// Define response +$response = (object) +[ + 'success' => true, + 'message' => false, + 'magnets' => [], +]; + +// Yggdrasil connections only +if (!Valid::host($_SERVER['REMOTE_ADDR'])) +{ + $response->success = false; + $response->message = _('Yggdrasil connection required to enable resource features'); +} + +// Init session +else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) +{ + $response->success = false; + $response->message = _('Could not init user session'); +} + +// Get user +else if (!$user = $db->getUser($userId)) +{ + $response->success = false; + $response->message = _('Could not init user info'); +} + +// On first visit, redirect user to the welcome page with access level question +else if (is_null($user->public) && !isset($_GET['rss'])) +{ + header( + sprintf('Location: %s/welcome.php', WEBSITE_URL) + ); +} + +// Request valid +else +{ + // Query is magnet link + if ($magnet = Yggverse\Parser\Magnet::is($request->query)) + { + header( + sprintf('Location: %s/action.php?target=magnet&toggle=new&magnet=%s', WEBSITE_URL, urlencode($request->query)) + ); + } + + // Get index + $response->total = $sphinx->searchMagnetsTotal($request->query); + $results = $sphinx->searchMagnets( + $request->query, + $request->page * WEBSITE_PAGINATION_LIMIT - WEBSITE_PAGINATION_LIMIT, + WEBSITE_PAGINATION_LIMIT, + $response->total + ); + + foreach ($results as $result) + { + if ($magnet = $db->getMagnet($result->magnetid)) + { + // Get access info + $accessRead = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved)); + $accessEdit = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST)); + + // Keywords + $keywords = []; + + foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $keyword) + { + $keywords[] = $db->getKeywordTopic($keyword->keywordTopicId)->value; + } + + $response->magnets[] = (object) + [ + 'magnetId' => $magnet->magnetId, + 'title' => $magnet->title ? htmlentities($magnet->title) : ($magnet->dn ? htmlentities($magnet->dn): false), + 'preview' => $magnet->preview ? nl2br( + htmlentities( + $magnet->preview + ) + ) : false, + 'approved' => (bool) $magnet->approved, + 'public' => (bool) $magnet->public, + 'sensitive' => (bool) $magnet->sensitive, + 'comments' => (bool) $magnet->comments, + 'timeAdded' => $magnet->timeAdded ? Time::ago((int) $magnet->timeAdded) : false, + 'timeUpdated' => $magnet->timeUpdated ? Time::ago((int) $magnet->timeUpdated) : false, + 'keywords' => $keywords, + 'comment' => (object) + [ + 'total' => $db->findMagnetCommentsTotalByMagnetId($magnet->magnetId), + 'status' => $db->findMagnetCommentsTotal($magnet->magnetId, $userId), + ], + 'download' => (object) + [ + 'total' => $db->findMagnetDownloadsTotalByMagnetId($magnet->magnetId), + 'status' => $db->findMagnetDownloadsTotal($magnet->magnetId, $userId), + ], + 'star' => (object) + [ + 'total' => $db->findMagnetStarsTotalByMagnetId($magnet->magnetId, true), + 'status' => $db->findLastMagnetStarValue($magnet->magnetId, $userId), + ], + 'access' => (object) + [ + 'read' => $accessRead, + 'edit' => $accessEdit, + ], + 'seeders' => $db->getMagnetToAddressTrackerSeedersSumByMagnetId($magnet->magnetId), + 'completed' => $db->getMagnetToAddressTrackerCompletedSumByMagnetId($magnet->magnetId), + 'leechers' => $db->getMagnetToAddressTrackerLeechersSumByMagnetId($magnet->magnetId), + 'directs' => $db->getMagnetToAcceptableSourceTotalByMagnetId($magnet->magnetId) + ]; + } + } +} + +if (isset($_GET['rss']) && $response->success) { ?>' . PHP_EOL ?> + + + + <?php echo !empty($request->query) ? sprintf(_('%s - Search - %s'), htmlspecialchars($request->query, ENT_QUOTES, 'UTF-8'), WEBSITE_NAME) + : WEBSITE_NAME ?> + + query))) ?> + magnets as $magnet) { ?> + access->read) { ?> + + <?php echo htmlspecialchars($magnet->title, ENT_QUOTES, 'UTF-8') ?> + preview), ENT_QUOTES, 'UTF-8') ?> + magnetId) ?> + magnetId) ?> + + + + + + + + + + + + + <?php echo sprintf(_('%s - Search - %s'), + htmlspecialchars($request->query, ENT_QUOTES, 'UTF-8'), + WEBSITE_NAME) ?> + + + + + + + + +
+
+
+
+ success) { ?> + magnets) { ?> + magnets as $magnet) { ?> + access->read) { ?> + +
+
+ +

title ?>

+ leechers && !$magnet->seeders) { ?> + + + + +
+
+ public) { ?> + + + + + + + + approved) { ?> + + + + + + + access->edit) { ?> + + + + + + + +
+ preview) { ?> +
preview ?>
+ + keywords) { ?> +
+ keywords as $keyword) { ?> + + # + + +
+ +
+ + + + timeUpdated ? _('Updated') : _('Added') ?> + timeUpdated ? $magnet->timeUpdated : $magnet->timeAdded ?> + + + + + + + seeders ?> + + + + + + completed ?> + + + + + + + leechers ?> + + directs) { ?> + + + + + directs ?> + + + + + + star->status) { ?> + + + + + + + + + + star->total ?> + + comments) { ?> + + + comment->status) { ?> + + + + + + + + + + comment->total ?> + + + + + download->status) { ?> + + + + + + + + + + download->total ?> + +
+
+ + + + + +
+

+ +

+
+
+ + +
+
message ?>
+
+ +
+
+ total > WEBSITE_PAGINATION_LIMIT) { ?> +
+
+ page, ceil($response->total / WEBSITE_PAGINATION_LIMIT)) ?> + page > 1) { ?> + + + + + page < ceil($response->total / WEBSITE_PAGINATION_LIMIT)) { ?> + + + + +
+
+ +
+
+
+
+
+
+ $tracker) { ?> + announce) && !empty($tracker->stats)) { ?> + + / + + | + + + + | + + | + + + + + | + + + | + +
+
+
+
+ + + \ No newline at end of file From a9f67243babb5749c157feec88e2205cabcf956f Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 23 Sep 2023 17:24:56 +0300 Subject: [PATCH 006/434] change css version #14 --- example/environment/env.example.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/environment/env.example.php b/example/environment/env.example.php index 0a2d22f..2e839d7 100644 --- a/example/environment/env.example.php +++ b/example/environment/env.example.php @@ -55,7 +55,7 @@ define('MEMCACHED_TIMEOUT', 60 * 5); // Webapp define('WEBSITE_URL', ''); define('WEBSITE_NAME', 'YGGtracker'); -define('WEBSITE_CSS_VERSION', 1); +define('WEBSITE_CSS_VERSION', 2); define('WEBSITE_PAGINATION_LIMIT', 20); From 41a557cbb67353d90dc6049d708bb6d90fdd38dc Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 23 Sep 2023 19:34:49 +0300 Subject: [PATCH 007/434] implement profile module #14 #15 --- src/app/controller/module/profile.php | 23 +++ .../view/theme/default/module/profile.phtml | 140 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/app/controller/module/profile.php create mode 100644 src/app/view/theme/default/module/profile.phtml diff --git a/src/app/controller/module/profile.php b/src/app/controller/module/profile.php new file mode 100644 index 0000000..60226c0 --- /dev/null +++ b/src/app/controller/module/profile.php @@ -0,0 +1,23 @@ +_user = $user; + } + + public function render() + { + $route = isset($_GET['_route_']) ? (string) $_GET['_route_'] : ''; + + $stars = $this->_user->getUserPageStarsTotal(); + $views = $this->_user->getUserPageViewsTotal(); + $downloads = $this->_user->getUserPageDownloadsTotal(); + $comments = $this->_user->getUserPageCommentsTotal(); + + include __DIR__ . '../../../view/theme/default/module/profile.phtml'; + } +} \ No newline at end of file diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml new file mode 100644 index 0000000..7dfcfc5 --- /dev/null +++ b/src/app/view/theme/default/module/profile.phtml @@ -0,0 +1,140 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file From cbf09026778d3ec77b33d64e72bef793f92c050b Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 23 Sep 2023 20:06:59 +0300 Subject: [PATCH 008/434] add edit history menu item #15 --- src/app/controller/module/profile.php | 1 + .../view/theme/default/module/profile.phtml | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/app/controller/module/profile.php b/src/app/controller/module/profile.php index 60226c0..7858fcd 100644 --- a/src/app/controller/module/profile.php +++ b/src/app/controller/module/profile.php @@ -17,6 +17,7 @@ class AppControllerModuleProfile $views = $this->_user->getUserPageViewsTotal(); $downloads = $this->_user->getUserPageDownloadsTotal(); $comments = $this->_user->getUserPageCommentsTotal(); + $editions = $this->_user->getUserPageEditionsTotal(); include __DIR__ . '../../../view/theme/default/module/profile.phtml'; } diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml index 7dfcfc5..b102482 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/src/app/view/theme/default/module/profile.phtml @@ -137,4 +137,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From c4f5409ffa8c07712a77b58f30b275e04c45d883 Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 23 Sep 2023 21:31:43 +0300 Subject: [PATCH 009/434] rename methods #15 --- src/app/controller/module/profile.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/controller/module/profile.php b/src/app/controller/module/profile.php index 7858fcd..a84364c 100644 --- a/src/app/controller/module/profile.php +++ b/src/app/controller/module/profile.php @@ -13,11 +13,11 @@ class AppControllerModuleProfile { $route = isset($_GET['_route_']) ? (string) $_GET['_route_'] : ''; - $stars = $this->_user->getUserPageStarsTotal(); - $views = $this->_user->getUserPageViewsTotal(); - $downloads = $this->_user->getUserPageDownloadsTotal(); - $comments = $this->_user->getUserPageCommentsTotal(); - $editions = $this->_user->getUserPageEditionsTotal(); + $stars = $this->_user->findUserPageStarsDistinctTotalByValue(true); + $views = $this->_user->findUserPageViewsDistinctTotal(); + $downloads = $this->_user->findUserPageDownloadsDistinctTotal(); + $comments = $this->_user->findUserPageCommentsDistinctTotal(); + $editions = $this->_user->findUserPageEditionsDistinctTotal(); include __DIR__ . '../../../view/theme/default/module/profile.phtml'; } From a600a08a28f4ef5177d06e035ce20c32aa561e6e Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 23 Sep 2023 21:37:52 +0300 Subject: [PATCH 010/434] init MVC framework refactory #14 --- src/app/controller/index.php | 116 +++++ src/app/controller/module/footer.php | 25 + src/app/controller/module/head.php | 54 +++ src/app/controller/module/header.php | 19 + src/app/controller/module/page.php | 9 + src/app/controller/module/pagination.php | 43 ++ src/app/controller/module/search.php | 11 + src/app/controller/response.php | 65 +++ src/app/controller/submit.php | 96 ++++ src/app/controller/user.php | 152 ++++++ src/app/controller/welcome.php | 119 +++++ src/{library => app/model}/database.php | 61 ++- src/{library => app/model}/sphinx.php | 2 +- src/app/view/theme/default/index.phtml | 32 ++ .../view/theme/default/module/footer.phtml | 27 ++ src/app/view/theme/default/module/head.phtml | 7 + .../view/theme/default/module/header.phtml | 10 + src/app/view/theme/default/module/page.phtml | 126 +++++ .../theme/default/module/pagination.phtml | 15 + .../view/theme/default/module/search.phtml | 4 + src/app/view/theme/default/response.phtml | 24 + src/app/view/theme/default/submit.phtml | 35 ++ src/app/view/theme/default/welcome.phtml | 39 ++ src/config/bootstrap.php | 112 +++-- .../assets/theme/default/css/framework.css | 77 +++- src/public/import.php | 303 ------------ src/public/index.php | 431 +----------------- src/public/welcome.php | 146 ------ 28 files changed, 1235 insertions(+), 925 deletions(-) create mode 100644 src/app/controller/index.php create mode 100644 src/app/controller/module/footer.php create mode 100644 src/app/controller/module/head.php create mode 100644 src/app/controller/module/header.php create mode 100644 src/app/controller/module/page.php create mode 100644 src/app/controller/module/pagination.php create mode 100644 src/app/controller/module/search.php create mode 100644 src/app/controller/response.php create mode 100644 src/app/controller/submit.php create mode 100644 src/app/controller/user.php create mode 100644 src/app/controller/welcome.php rename src/{library => app/model}/database.php (96%) rename src/{library => app/model}/sphinx.php (99%) create mode 100644 src/app/view/theme/default/index.phtml create mode 100644 src/app/view/theme/default/module/footer.phtml create mode 100644 src/app/view/theme/default/module/head.phtml create mode 100644 src/app/view/theme/default/module/header.phtml create mode 100644 src/app/view/theme/default/module/page.phtml create mode 100644 src/app/view/theme/default/module/pagination.phtml create mode 100644 src/app/view/theme/default/module/search.phtml create mode 100644 src/app/view/theme/default/response.phtml create mode 100644 src/app/view/theme/default/submit.phtml create mode 100644 src/app/view/theme/default/welcome.phtml delete mode 100644 src/public/import.php delete mode 100644 src/public/welcome.php diff --git a/src/app/controller/index.php b/src/app/controller/index.php new file mode 100644 index 0000000..6be1c5b --- /dev/null +++ b/src/app/controller/index.php @@ -0,0 +1,116 @@ +_db = new Database( + DB_HOST, + DB_PORT, + DB_NAME, + DB_USERNAME, + DB_PASSWORD + ); + + $this->_sphinx = new Sphinx( + SPHINX_HOST, + SPHINX_PORT + ); + + $this->_memory = new \Yggverse\Cache\Memory( + MEMCACHED_HOST, + MEMCACHED_PORT, + MEMCACHED_NAMESPACE, + MEMCACHED_TIMEOUT + time() + ); + } + + catch (Exception $error) + { + require_once __DIR__ . '/error/500.php'; + + $controller = new AppControllerError500( + print_r($error, true) + ); + + $controller->render(); + + exit; + } + } + + public function render() + { + $page = isset($_GET['page']) ? (int) $_GET['page'] : 1; + + $pages = []; + + require_once __DIR__ . '/module/pagination.php'; + + $appControllerModulePagination = new appControllerModulePagination(); + + require_once __DIR__ . '/module/head.php'; + + $appControllerModuleHead = new AppControllerModuleHead( + WEBSITE_URL, + $page > 1 ? + sprintf( + _('Page %s - BitTorrent Registry for Yggdrasil - %s'), + $page, + WEBSITE_NAME + ) : + sprintf( + _('%s - BitTorrent Registry for Yggdrasil'), + WEBSITE_NAME + ), + [ + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/common.css?%s', + WEBSITE_CSS_VERSION + ), + ], + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/framework.css?%s', + WEBSITE_CSS_VERSION + ), + ], + ] + ); + + require_once __DIR__ . '/module/profile.php'; + + $appControllerModuleProfile = new AppControllerModuleProfile($user->userId); + + require_once __DIR__ . '/module/header.php'; + + $appControllerModuleHeader = new AppControllerModuleHeader(); + + require_once __DIR__ . '/module/footer.php'; + + $appControllerModuleFooter = new AppControllerModuleFooter(); + + include __DIR__ . '/../view/theme/default/index.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/module/footer.php b/src/app/controller/module/footer.php new file mode 100644 index 0000000..d1a1f81 --- /dev/null +++ b/src/app/controller/module/footer.php @@ -0,0 +1,25 @@ +announce) && !empty($tracker->stats)) + { + $response['trackers'][] = [ + 'announce' => $tracker->announce, + 'stats' => $tracker->stats, + ]; + } + } + } + + include __DIR__ . '../../../view/theme/default/module/footer.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/module/head.php b/src/app/controller/module/head.php new file mode 100644 index 0000000..781873b --- /dev/null +++ b/src/app/controller/module/head.php @@ -0,0 +1,54 @@ +setBase($base); + $this->setTitle($title); + + foreach ($links as $link) + { + $this->addLink( + $link['rel'], + $link['type'], + $link['href'], + ); + } + } + + public function setBase(string $base) : void + { + $this->_base = $base; + } + + public function setTitle(string $title) : void + { + $this->_title = $title; + } + + public function addLink(string $rel, string $type, string $href) : void + { + $this->_links[] = (object) + [ + 'rel' => $rel, + 'type' => $type, + 'href' => $href, + ]; + } + + public function render() + { + $base = $this->_base; + + $links = $this->_links; + + $title = htmlentities($this->_title); + + include __DIR__ . '../../../view/theme/default/module/head.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/module/header.php b/src/app/controller/module/header.php new file mode 100644 index 0000000..df162b1 --- /dev/null +++ b/src/app/controller/module/header.php @@ -0,0 +1,19 @@ +YGG', + WEBSITE_NAME + ); + + require_once __DIR__ . '/search.php'; + + $appControllerModuleSearch = new AppControllerModuleSearch(); + + include __DIR__ . '../../../view/theme/default/module/header.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/module/page.php b/src/app/controller/module/page.php new file mode 100644 index 0000000..036a6e7 --- /dev/null +++ b/src/app/controller/module/page.php @@ -0,0 +1,9 @@ + $limit) + { + parse_str($url, $query); + + $pagination->page = isset($query['total']) ? (int) $query['total'] : 1; + $pagination->pages = ceil($total / $limit); + + // Previous + if ($page > 1) + { + $query['page'] = $page - 1; + + $pagination->back = sprintf('%s', WEBSITE_URL, http_build_query($query)); + } + + else + { + $pagination->back = false; + } + + // Next + if ($page < ceil($total / $limit)) + { + $query['page'] = $page + 1; + + $pagination->next = sprintf('%s', WEBSITE_URL, http_build_query($query)); + } + + else + { + $pagination->next = false; + } + + // Render + } + } +} \ No newline at end of file diff --git a/src/app/controller/module/search.php b/src/app/controller/module/search.php new file mode 100644 index 0000000..cf5ea4a --- /dev/null +++ b/src/app/controller/module/search.php @@ -0,0 +1,11 @@ +_title = $title; + $this->_h1 = $h1; + $this->_text = $text; + $this->_code = $code; + } + + public function render() + { + header( + sprintf( + 'HTTP/1.0 %s Not Found', + $this->_code + ) + ); + + $h1 = $this->_h1; + $text = $this->_text; + + require_once __DIR__ . '/module/head.php'; + + $appControllerModuleHead = new AppControllerModuleHead( + WEBSITE_URL, + $this->_title, + [ + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/common.css?%s', + WEBSITE_CSS_VERSION + ), + ], + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/framework.css?%s', + WEBSITE_CSS_VERSION + ), + ], + ] + ); + + require_once __DIR__ . '/module/header.php'; + + $appControllerModuleHeader = new AppControllerModuleHeader(); + + require_once __DIR__ . '/module/footer.php'; + + $appControllerModuleFooter = new AppControllerModuleFooter(); + + include __DIR__ . '../../view/theme/default/response.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/submit.php b/src/app/controller/submit.php new file mode 100644 index 0000000..dddf4fc --- /dev/null +++ b/src/app/controller/submit.php @@ -0,0 +1,96 @@ +render(); + + exit; + } + + public function render() + { + require_once __DIR__ . '/user.php'; + + $appControllerUser = new AppControllerUser( + $_SERVER['REMOTE_ADDR'] + ); + + // Get user info + if (!$user = $appControllerUser->getUser()) + { + $this->_response( + sprintf( + _('Error - %s'), + WEBSITE_NAME + ), + _('500'), + _('Could not init user'), + 500 + ); + } + + // Require account type selection + if (is_null($user->public)) + { + header( + sprintf('Location: %s/welcome', trim(WEBSITE_URL, '/')) + ); + } + + // Render + require_once __DIR__ . '/module/head.php'; + + $appControllerModuleHead = new AppControllerModuleHead( + WEBSITE_URL, + sprintf( + _('Submit - %s'), + WEBSITE_NAME + ), + [ + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/common.css?%s', + WEBSITE_CSS_VERSION + ), + ], + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/framework.css?%s', + WEBSITE_CSS_VERSION + ), + ], + ] + ); + + require_once __DIR__ . '/module/profile.php'; + + $appControllerModuleProfile = new AppControllerModuleProfile( + $appControllerUser + ); + + require_once __DIR__ . '/module/header.php'; + + $appControllerModuleHeader = new AppControllerModuleHeader(); + + require_once __DIR__ . '/module/footer.php'; + + $appControllerModuleFooter = new AppControllerModuleFooter(); + + include __DIR__ . '../../view/theme/default/submit.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/user.php b/src/app/controller/user.php new file mode 100644 index 0000000..ba58cdb --- /dev/null +++ b/src/app/controller/user.php @@ -0,0 +1,152 @@ +_database = new AppModelDatabase( + DB_HOST, + DB_PORT, + DB_NAME, + DB_USERNAME, + DB_PASSWORD + ); + } + + catch (Exception $error) + { + $this->_response( + sprintf( + _('Error - %s'), + WEBSITE_NAME + ), + _('500'), + print_r($error, true), + 500 + ); + } + + // Validate user address + require_once __DIR__ . '/../../library/valid.php'; + + $error = []; + if (!Valid::host($address, $error)) + { + $this->_response( + sprintf( + _('Error - %s'), + WEBSITE_NAME + ), + _('406'), + print_r($error, true), + 406 + ); + } + + // Init user session + try + { + $this->_database->beginTransaction(); + + $this->_user = $this->_database->getUser( + $this->_database->initUserId( + $address, + USER_DEFAULT_APPROVED, + time() + ) + ); + + $this->_database->commit(); + } + + catch (Exception $error) + { + $this->_database->rollback(); + + $this->_response( + sprintf( + _('Error - %s'), + WEBSITE_NAME + ), + _('500'), + print_r($error, true), + 500 + ); + } + } + + private function _response(string $title, string $h1, string $text, int $code = 200) + { + require_once __DIR__ . '/response.php'; + + $appControllerResponse = new AppControllerResponse( + $title, + $h1, + $text, + $code + ); + + $appControllerResponse->render(); + + exit; + } + + public function getUser() + { + return $this->_user; + } + + public function findUserPageStarsDistinctTotalByValue(bool $value) : int + { + return $this->_database->findUserPageStarsDistinctTotal( + $this->_user->userId, + $value + ); + } + + public function findUserPageViewsDistinctTotal() : int + { + return $this->_database->findUserPageViewsDistinctTotal( + $this->_user->userId + ); + } + + public function findUserPageDownloadsDistinctTotal() : int + { + return $this->_database->findUserPageDownloadsDistinctTotal( + $this->_user->userId + ); + } + + public function findUserPageCommentsDistinctTotal() : int + { + return $this->_database->findUserPageCommentsDistinctTotal( + $this->_user->userId + ); + } + + public function findUserPageEditionsDistinctTotal() : int + { + return $this->_database->findUserPageEditionsDistinctTotal( + $this->_user->userId + ); + } + + public function updateUserPublic(bool $public, int $time) : int + { + return $this->_database->updateUserPublic( + $this->_user->userId, + $public, + $time + ); + } +} \ No newline at end of file diff --git a/src/app/controller/welcome.php b/src/app/controller/welcome.php new file mode 100644 index 0000000..611be0d --- /dev/null +++ b/src/app/controller/welcome.php @@ -0,0 +1,119 @@ +_user = new AppModelUser( + $_SERVER['REMOTE_ADDR'] + ); + } + + private function _response(string $title, string $h1, string $text, int $code = 200) + { + require_once __DIR__ . '/response.php'; + + $appControllerResponse = new AppControllerResponse( + $title, + $h1, + $text, + $code + ); + + $appControllerResponse->render(); + + exit; + } + + public function render() + { + if (!$user = $this->_user->get()) + { + $this->_response( + sprintf( + _('Error - %s'), + WEBSITE_NAME + ), + _('500'), + _('Could not init user'), + 500 + ); + } + + if (!is_null($user->public)) + { + $this->_response( + sprintf( + _('Welcome back - %s'), + WEBSITE_NAME + ), + _('Welcome back!'), + sprintf( + _('You already have selected account type to %s'), + $user->public ? _('Distributed') : _('Local') + ), + 405 + ); + } + + if (isset($_POST['public'])) + { + if ($this->_user->updateUserPublic((bool) $_POST['public'], time())) + { + $this->_response( + sprintf( + _('Success - %s'), + WEBSITE_NAME + ), + _('Success!'), + sprintf( + _('Account type successfully changed to %s'), + $_POST['public'] ? _('Distributed') : _('Local') + ), + ); + } + } + + require_once __DIR__ . '/module/head.php'; + + $appControllerModuleHead = new AppControllerModuleHead( + WEBSITE_URL, + sprintf( + _('Welcome to %s'), + WEBSITE_NAME + ), + [ + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/common.css?%s', + WEBSITE_CSS_VERSION + ), + ], + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/framework.css?%s', + WEBSITE_CSS_VERSION + ), + ], + ] + ); + + require_once __DIR__ . '/module/header.php'; + + $appControllerModuleHeader = new AppControllerModuleHeader(); + + require_once __DIR__ . '/module/footer.php'; + + $appControllerModuleFooter = new AppControllerModuleFooter(); + + include __DIR__ . '../../view/theme/default/welcome.phtml'; + } +} \ No newline at end of file diff --git a/src/library/database.php b/src/app/model/database.php similarity index 96% rename from src/library/database.php rename to src/app/model/database.php index ccb6c0e..ec435cb 100644 --- a/src/library/database.php +++ b/src/app/model/database.php @@ -1,6 +1,6 @@ rowCount(); } + public function findUserPageStarsDistinctTotal(int $userId, bool $value) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `userPageStar` WHERE `userId` = ? AND `value` = ?'); + + $query->execute([$userId, (int) $value]); + + return $query->fetch()->result; + } + + public function findUserPageViewsDistinctTotal(int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `userPageView` WHERE `userId` = ?'); + + $query->execute([$userId]); + + return $query->fetch()->result; + } + + public function findUserPageDownloadsDistinctTotal(int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `userPageDownload` WHERE `userId` = ?'); + + $query->execute([$userId]); + + return $query->fetch()->result; + } + + public function findUserPageCommentsDistinctTotal(int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `userPageComment` WHERE `userId` = ?'); + + $query->execute([$userId]); + + return $query->fetch()->result; + } + + public function findUserPageEditionsDistinctTotal(int $userId) : int { + + return 0; + + /* @TODO + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `userPageEdition` WHERE `userId` = ?'); + + $query->execute([$userId]); + + return $query->fetch()->result; + */ + } + // Magnet public function addMagnet(int $userId, int $xl, diff --git a/src/library/sphinx.php b/src/app/model/sphinx.php similarity index 99% rename from src/library/sphinx.php rename to src/app/model/sphinx.php index 6cb15ae..db2f8be 100644 --- a/src/library/sphinx.php +++ b/src/app/model/sphinx.php @@ -1,6 +1,6 @@ + + render() ?> + + render() ?> +
+
+
+
+ render() ?> + + + render($page->pageId) ?> + + render() ?> + +
+

+ +

+
+ +
+
+ +
+
+
+
+ render() ?> + + \ No newline at end of file diff --git a/src/app/view/theme/default/module/footer.phtml b/src/app/view/theme/default/module/footer.phtml new file mode 100644 index 0000000..75aeaf3 --- /dev/null +++ b/src/app/view/theme/default/module/footer.phtml @@ -0,0 +1,27 @@ +
+
+
+
+ $tracker) { ?> + + / + + | + + + | + + | + + + | + + + | + +
+
+
+
+ + \ No newline at end of file diff --git a/src/app/view/theme/default/module/head.phtml b/src/app/view/theme/default/module/head.phtml new file mode 100644 index 0000000..70c6452 --- /dev/null +++ b/src/app/view/theme/default/module/head.phtml @@ -0,0 +1,7 @@ + + + <?php echo $title ?> + + + + \ No newline at end of file diff --git a/src/app/view/theme/default/module/header.phtml b/src/app/view/theme/default/module/header.phtml new file mode 100644 index 0000000..1c622e9 --- /dev/null +++ b/src/app/view/theme/default/module/header.phtml @@ -0,0 +1,10 @@ +
+
+
+ + render() ?> +
+
+
\ No newline at end of file diff --git a/src/app/view/theme/default/module/page.phtml b/src/app/view/theme/default/module/page.phtml new file mode 100644 index 0000000..f242004 --- /dev/null +++ b/src/app/view/theme/default/module/page.phtml @@ -0,0 +1,126 @@ + +
+
+ +

+ + + + + +
+
+ + + + + + + + + + + + + +
+ preview) { ?> +
preview ?>
+ + keywords) { ?> +
+ keywords as $keyword) { ?> + + # + + +
+ +
+ + + timeUpdated ? _('Updated') : _('Added') ?> + timeUpdated ? $magnet->timeUpdated : $magnet->timeAdded ?> + + + + + + + seeders ?> + + + + + + completed ?> + + + + + + + leechers ?> + + directs) { ?> + + + + + directs ?> + + + + + star->status) { ?> + + + + + + + + + + star->total ?> + + comments) { ?> + + + comment->status) { ?> + + + + + + + + + + comment->total ?> + + + + + download->status) { ?> + + + + + + + + + + download->total ?> + +
+
\ No newline at end of file diff --git a/src/app/view/theme/default/module/pagination.phtml b/src/app/view/theme/default/module/pagination.phtml new file mode 100644 index 0000000..1f8422a --- /dev/null +++ b/src/app/view/theme/default/module/pagination.phtml @@ -0,0 +1,15 @@ +
+
+ page, $pagination->total) ?> + back) { ?> + + + + + next) { ?> + + + + +
+
\ No newline at end of file diff --git a/src/app/view/theme/default/module/search.phtml b/src/app/view/theme/default/module/search.phtml new file mode 100644 index 0000000..540e606 --- /dev/null +++ b/src/app/view/theme/default/module/search.phtml @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/src/app/view/theme/default/response.phtml b/src/app/view/theme/default/response.phtml new file mode 100644 index 0000000..2438208 --- /dev/null +++ b/src/app/view/theme/default/response.phtml @@ -0,0 +1,24 @@ + + + render() ?> + + render() ?> +
+
+
+
+
+

+ +

+
+ +
+
+
+
+
+
+ render() ?> + + \ No newline at end of file diff --git a/src/app/view/theme/default/submit.phtml b/src/app/view/theme/default/submit.phtml new file mode 100644 index 0000000..9b28efa --- /dev/null +++ b/src/app/view/theme/default/submit.phtml @@ -0,0 +1,35 @@ + + + render() ?> + + render() ?> +
+
+
+
+ render() ?> +
+
+

+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+
+ render() ?> + + \ No newline at end of file diff --git a/src/app/view/theme/default/welcome.phtml b/src/app/view/theme/default/welcome.phtml new file mode 100644 index 0000000..af9aa03 --- /dev/null +++ b/src/app/view/theme/default/welcome.phtml @@ -0,0 +1,39 @@ + + + render() ?> + + render() ?> +
+
+
+
+
+
+

+
+

+

+

address ?>

+
+
+ + +
+
+ +
+
+
+
+
+
+
+ render() ?> + + \ No newline at end of file diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index 70679bd..cd88737 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -38,50 +38,90 @@ if (!file_exists(__DIR__ . '/env.' . PHP_ENV . '.php')) // Load environment require_once __DIR__ . '/env.' . PHP_ENV . '.php'; -// Local internal dependencies -require_once __DIR__ . '/../library/database.php'; -require_once __DIR__ . '/../library/sphinx.php'; -require_once __DIR__ . '/../library/scrapeer.php'; -require_once __DIR__ . '/../library/time.php'; -require_once __DIR__ . '/../library/curl.php'; -require_once __DIR__ . '/../library/valid.php'; -require_once __DIR__ . '/../library/filter.php'; +// Route +parse_str($_SERVER['QUERY_STRING'], $request); -// Vendors autoload -require_once __DIR__ . '/../../vendor/autoload.php'; +if (isset($request['_route_'])) +{ + switch ($request['_route_']) + { + case 'stars': -// Connect database -try { + require_once(__DIR__ . '/../app/controller/stars.php'); - $db = new Database(DB_HOST, DB_PORT, DB_NAME, DB_USERNAME, DB_PASSWORD); + $controller = new AppControllerStars(); -} catch (Exception $e) { + break; - var_dump($e); + case 'views': - exit; + require_once(__DIR__ . '/../app/controller/views.php'); + + $controller = new AppControllerViews(); + + break; + + case 'downloads': + + require_once(__DIR__ . '/../app/controller/downloads.php'); + + $controller = new AppControllerDownloads(); + + break; + + case 'comments': + + require_once(__DIR__ . '/../app/controller/comments.php'); + + $controller = new AppControllerComments(); + + break; + + case 'editions': + + require_once(__DIR__ . '/../app/controller/editions.php'); + + $controller = new AppControllerEditions(); + + break; + + case 'welcome': + + require_once(__DIR__ . '/../app/controller/welcome.php'); + + $controller = new AppControllerWelcome(); + + break; + + case 'submit': + + require_once(__DIR__ . '/../app/controller/submit.php'); + + $controller = new AppControllerSubmit(); + + break; + + default: + + require_once(__DIR__ . '/../app/controller/response.php'); + + $controller = new AppControllerResponse( + sprintf( + _('404 - Not found - %s'), + WEBSITE_NAME + ), + _('404'), + _('Page not found'), + 404 + ); + } } -// Connect Sphinx -try { +else +{ + require_once(__DIR__ . '/../app/controller/index.php'); - $sphinx = new Sphinx(SPHINX_HOST, SPHINX_PORT); - -} catch(Exception $e) { - - var_dump($e); - - exit; + $controller = new AppControllerIndex(); } -// Connect memcached -try { - - $memory = new Yggverse\Cache\Memory(MEMCACHED_HOST, MEMCACHED_PORT, MEMCACHED_NAMESPACE, MEMCACHED_TIMEOUT + time()); - -} catch(Exception $e) { - - var_dump($e); - - exit; -} \ No newline at end of file +$controller->render(); \ No newline at end of file diff --git a/src/public/assets/theme/default/css/framework.css b/src/public/assets/theme/default/css/framework.css index 9b3ebff..4f5968b 100644 --- a/src/public/assets/theme/default/css/framework.css +++ b/src/public/assets/theme/default/css/framework.css @@ -32,22 +32,46 @@ text-align: right; } -.text-color-green { +.text-color-green, +a.text-color-green, +a.text-color-green:active, +a.text-color-green:visited, +a.text-color-green:hover { color: #96d9a1; } -.text-color-red { +.text-color-red, +a.text-color-red, +a.text-color-red:active, +a.text-color-red:visited, +a.text-color-red:hover { color: #d77575; } -.text-color-pink { +.text-color-pink, +a.text-color-pink, +a.text-color-pink:active, +a.text-color-pink:visited, +a.text-color-pink:hover { color: #b55cab; } -.text-color-default { +.text-color-default, +a.text-color-default, +a.text-color-default:active, +a.text-color-default:visited, +a.text-color-default:hover { color: #ccc; } +.text-color-white, +a.text-color-white, +a.text-color-white:active, +a.text-color-white:visited, +a.text-color-white:hover { + color: #fff; +} + /* .text-color-pink { color: #a44399; @@ -67,7 +91,11 @@ border-radius: 3px; } -.label-green { +.label-green, +a.label-green, +a.label-green:active, +a.label-green:visited, +a.label-green:hover { color: #fff; background-color: #65916d; } @@ -76,6 +104,10 @@ position: relative; } +.position-fixed { + position: fixed; +} + .top--2 { top: -2px; } @@ -120,11 +152,17 @@ background-color: #34384f; } -/* -.background-color-hover-night-light:hover { - background-color: #363a51; +.background-color-green { + background-color: #65916d; +} + +.background-color-hover-night-light:hover, +a.background-color-hover-night-light:hover, +a:active.background-color-hover-night-light:hover, +a:visited.background-color-hover-night-light:hover { + /*color: #fff;*/ + background-color: #3d4159; } -*/ .background-color-red { background-color: #9b4a4a; @@ -186,6 +224,11 @@ padding-right: 8px; } +.padding-y-6 { + padding-top: 6px; + padding-bottom: 6px; +} + .padding-8 { padding: 8px; } @@ -254,6 +297,10 @@ margin-left: 12px; } +.margin-l--176 { + margin-left: -176px; +} + .margin-y-8 { margin-top: 8px; margin-bottom: 8px; @@ -319,10 +366,22 @@ width: 50%; } +.width-20 { + width: 20%; +} + +.width-80 { + width: 80%; +} + .width-13px { width: 13px; } +.width-160-px { + width: 160px; +} + @media (max-width: 1220px) { .width-tablet-100 { diff --git a/src/public/import.php b/src/public/import.php deleted file mode 100644 index a233cd9..0000000 --- a/src/public/import.php +++ /dev/null @@ -1,303 +0,0 @@ - - true, - 'message' => _('Internal server error'), -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required for this action'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Init user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not get user session'); -} - -// On first visit, redirect user to the welcome page with access level question -else if (is_null($user->public)) -{ - header( - sprintf('Location: %s/welcome.php', WEBSITE_URL) - ); -} - -// Import form magnet link request -else if (!empty($_POST['magnet'])) -{ - // Validate magnet - if (!$magnet = Yggverse\Parser\Magnet::parse($_POST['magnet'])) - { - $response->success = false; - $response->message = _('Could not parse magnet link'); - } - - // Request valid - else - { - // Begin magnet registration - try - { - $db->beginTransaction(); - - // Init magnet - if ($magnetId = $db->addMagnet( $user->userId, - $magnet->xl, - $magnet->dn, - '', // @TODO deprecated, remove - MAGNET_DEFAULT_PUBLIC, - MAGNET_DEFAULT_COMMENTS, - MAGNET_DEFAULT_SENSITIVE, - $user->approved ? true : MAGNET_DEFAULT_APPROVED, - time())) - { - foreach ($magnet as $key => $value) - { - switch ($key) - { - case 'xt': - foreach ($value as $xt) - { - if (Yggverse\Parser\Magnet::isXTv1($xt)) - { - $db->addMagnetToInfoHash( - $magnetId, - $db->initInfoHashId( - Yggverse\Parser\Magnet::filterInfoHash($xt), 1 - ) - ); - } - if (Yggverse\Parser\Magnet::isXTv2($xt)) - { - $db->addMagnetToInfoHash( - $magnetId, - $db->initInfoHashId( - Yggverse\Parser\Magnet::filterInfoHash($xt), 2 - ) - ); - } - } - break; - case 'tr': - foreach ($value as $tr) - { - if (Valid::url($tr)) - { - if ($url = Yggverse\Parser\Url::parse($tr)) - { - $db->initMagnetToAddressTrackerId( - $magnetId, - $db->initAddressTrackerId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - } - break; - case 'ws': - foreach ($value as $ws) - { - // @TODO - } - break; - case 'as': - foreach ($value as $as) - { - if (Valid::url($as)) - { - if ($url = Yggverse\Parser\Url::parse($as)) - { - $db->initMagnetToAcceptableSourceId( - $magnetId, - $db->initAcceptableSourceId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - } - break; - case 'xs': - foreach ($value as $xs) - { - if (Valid::url($xs)) - { - if ($url = Yggverse\Parser\Url::parse($xs)) - { - $db->initMagnetToExactSourceId( - $magnetId, - $db->initExactSourceId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - } - break; - case 'mt': - foreach ($value as $mt) - { - // @TODO - } - break; - case 'x.pe': - foreach ($value as $xPe) - { - // @TODO - } - break; - case 'kt': - foreach ($value as $kt) - { - $db->initMagnetToKeywordTopicId( - $magnetId, - $db->initKeywordTopicId(trim(mb_strtolower(strip_tags(html_entity_decode($kt))))) - ); - } - break; - } - } - - $db->commit(); - } - } - - catch (Exception $error) - { - $response->success = false; - $response->message = sprintf( - _('Internal server error: %s'), - print_r($error, true) - ); - - $db->rollBack(); - } - } - - // Redirect to edit page on success - if ($response->success) - { - header(sprintf('Location: %s/edit.php?magnetId=%s', trim(WEBSITE_URL, '/'), $magnetId)); - } -} - -// Import form torrent file request -else if (!empty($_FILE['torrent'])) -{ - // @TODO -} - -?> - - - - - - - <?php echo sprintf(_('Add - %s'), WEBSITE_NAME) ?> - - - - - - -
-
- -
-
-
-
-
-
-
- success) { ?> -
-

-
-
-
- - -
-
- - -
-
- -
-
- -
- message ?> -
- -
-
-
-
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - \ No newline at end of file diff --git a/src/public/index.php b/src/public/index.php index 13cf940..6a9b6e3 100644 --- a/src/public/index.php +++ b/src/public/index.php @@ -1,431 +1,4 @@ false, - 'page' => 1, -]; - -// Prepare request -$request->query = isset($_GET['query']) ? urldecode((string) $_GET['query']) : ''; -$request->page = isset($_GET['page']) && $_GET['page'] > 0 ? (int) $_GET['page'] : 1; - -// Define response -$response = (object) -[ - 'success' => true, - 'message' => false, - 'magnets' => [], -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required to enable resource features'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Get user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not init user info'); -} - -// On first visit, redirect user to the welcome page with access level question -else if (is_null($user->public) && !isset($_GET['rss'])) -{ - header( - sprintf('Location: %s/welcome.php', WEBSITE_URL) - ); -} - -// Request valid -else -{ - // Query is magnet link - if ($magnet = Yggverse\Parser\Magnet::is($request->query)) - { - header( - sprintf('Location: %s/action.php?target=magnet&toggle=new&magnet=%s', WEBSITE_URL, urlencode($request->query)) - ); - } - - // Get index - $response->total = $sphinx->searchMagnetsTotal($request->query); - $results = $sphinx->searchMagnets( - $request->query, - $request->page * WEBSITE_PAGINATION_LIMIT - WEBSITE_PAGINATION_LIMIT, - WEBSITE_PAGINATION_LIMIT, - $response->total - ); - - foreach ($results as $result) - { - if ($magnet = $db->getMagnet($result->magnetid)) - { - // Get access info - $accessRead = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved)); - $accessEdit = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST)); - - // Keywords - $keywords = []; - - foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $keyword) - { - $keywords[] = $db->getKeywordTopic($keyword->keywordTopicId)->value; - } - - $response->magnets[] = (object) - [ - 'magnetId' => $magnet->magnetId, - 'title' => $magnet->title ? htmlentities($magnet->title) : ($magnet->dn ? htmlentities($magnet->dn): false), - 'preview' => $magnet->preview ? nl2br( - htmlentities( - $magnet->preview - ) - ) : false, - 'approved' => (bool) $magnet->approved, - 'public' => (bool) $magnet->public, - 'sensitive' => (bool) $magnet->sensitive, - 'comments' => (bool) $magnet->comments, - 'timeAdded' => $magnet->timeAdded ? Time::ago((int) $magnet->timeAdded) : false, - 'timeUpdated' => $magnet->timeUpdated ? Time::ago((int) $magnet->timeUpdated) : false, - 'keywords' => $keywords, - 'comment' => (object) - [ - 'total' => $db->findMagnetCommentsTotalByMagnetId($magnet->magnetId), - 'status' => $db->findMagnetCommentsTotal($magnet->magnetId, $userId), - ], - 'download' => (object) - [ - 'total' => $db->findMagnetDownloadsTotalByMagnetId($magnet->magnetId), - 'status' => $db->findMagnetDownloadsTotal($magnet->magnetId, $userId), - ], - 'star' => (object) - [ - 'total' => $db->findMagnetStarsTotalByMagnetId($magnet->magnetId, true), - 'status' => $db->findLastMagnetStarValue($magnet->magnetId, $userId), - ], - 'access' => (object) - [ - 'read' => $accessRead, - 'edit' => $accessEdit, - ], - 'seeders' => $db->getMagnetToAddressTrackerSeedersSumByMagnetId($magnet->magnetId), - 'completed' => $db->getMagnetToAddressTrackerCompletedSumByMagnetId($magnet->magnetId), - 'leechers' => $db->getMagnetToAddressTrackerLeechersSumByMagnetId($magnet->magnetId), - 'directs' => $db->getMagnetToAcceptableSourceTotalByMagnetId($magnet->magnetId) - ]; - } - } -} - -if (isset($_GET['rss']) && $response->success) { ?>' . PHP_EOL ?> - - - - <?php echo !empty($request->query) ? sprintf(_('%s - Search - %s'), htmlspecialchars($request->query, ENT_QUOTES, 'UTF-8'), WEBSITE_NAME) - : WEBSITE_NAME ?> - - query ? sprintf('?query=%s', urlencode($request->query)) : false) ?> - magnets as $magnet) { ?> - access->read) { ?> - - <?php echo htmlspecialchars($magnet->title, ENT_QUOTES, 'UTF-8') ?> - preview), ENT_QUOTES, 'UTF-8') ?> - magnetId) ?> - magnetId) ?> - - - - - - - - - - - - - <?php echo !empty($request->query) ? sprintf(_('%s - Search - %s'), htmlspecialchars($request->query, ENT_QUOTES, 'UTF-8'), WEBSITE_NAME) - : sprintf(_('%s - BitTorrent Registry for Yggdrasil'), WEBSITE_NAME) ?> - - - - - - - -
-
- -
-
-
-
-
-
- success) { ?> - magnets) { ?> - magnets as $magnet) { ?> - access->read) { ?> - -
-
- -

title ?>

- leechers && !$magnet->seeders) { ?> - - - - -
-
- public) { ?> - - - - - - - - approved) { ?> - - - - - - - access->edit) { ?> - - - - - - - -
- preview) { ?> -
preview ?>
- - keywords) { ?> -
- keywords as $keyword) { ?> - - # - - -
- -
- - - - timeUpdated ? _('Updated') : _('Added') ?> - timeUpdated ? $magnet->timeUpdated : $magnet->timeAdded ?> - - - - - - - seeders ?> - - - - - - completed ?> - - - - - - - leechers ?> - - directs) { ?> - - - - - directs ?> - - - - - - star->status) { ?> - - - - - - - - - - star->total ?> - - comments) { ?> - - - comment->status) { ?> - - - - - - - - - - comment->total ?> - - - - - download->status) { ?> - - - - - - - - - - download->total ?> - -
-
- - - - - -
-

- -

-
-
- - -
-
message ?>
-
- -
-
- total > WEBSITE_PAGINATION_LIMIT) { ?> -
-
- page, ceil($response->total / WEBSITE_PAGINATION_LIMIT)) ?> - page > 1) { ?> - - - - - page < ceil($response->total / WEBSITE_PAGINATION_LIMIT)) { ?> - - - - -
-
- -
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - - \ No newline at end of file +// Bootstrap application +require_once __DIR__ . '/../config/bootstrap.php'; \ No newline at end of file diff --git a/src/public/welcome.php b/src/public/welcome.php deleted file mode 100644 index aaac011..0000000 --- a/src/public/welcome.php +++ /dev/null @@ -1,146 +0,0 @@ - - true, - 'message' => _('Internal server error'), -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required for this action'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Init user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not get user session'); -} - -// User can change public level once, because by agreement data could be already sent -// Otherwise, local access level could be changed to public on settings page later -// Redirect to website features -else if (!is_null($user->public)) -{ - header( - sprintf('Location: %s', WEBSITE_URL) - ); -} - -// Apply answer on form submit -else if (isset($_POST['public'])) -{ - if ($db->updateUserPublic($user->userId, (bool) $_POST['public'], time())) - { - header( - sprintf('Location: %s', WEBSITE_URL) - ); - } -} - -?> - - - - - - - <?php echo sprintf(_('Welcome to %s'), WEBSITE_NAME) ?> - - - - - - -
-
- -
-
-
-
-
-
-
- success) { ?> -
-
-

-
-

-

-

address ?>

-
-
- - -
-
- -
-
-
- -
message ?>
- -
-
-
-
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - \ No newline at end of file From 8740e7a63f53440367c38ee91686bfb0933446ac Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 00:18:16 +0300 Subject: [PATCH 011/434] add user avatar #14 #15 --- src/app/controller/module/profile.php | 5 ++++ src/app/controller/user.php | 25 +++++++++++++++++++ .../view/theme/default/module/profile.phtml | 9 ++++++- src/config/bootstrap.php | 3 +++ .../assets/theme/default/css/framework.css | 23 ++++++++++++++++- 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/app/controller/module/profile.php b/src/app/controller/module/profile.php index a84364c..9300539 100644 --- a/src/app/controller/module/profile.php +++ b/src/app/controller/module/profile.php @@ -19,6 +19,11 @@ class AppControllerModuleProfile $comments = $this->_user->findUserPageCommentsDistinctTotal(); $editions = $this->_user->findUserPageEditionsDistinctTotal(); + $identicon = $this->_user->getIdenticon(24); + + $public = $this->_user->getPublic(); + $address = $this->_user->getAddress(); + include __DIR__ . '../../../view/theme/default/module/profile.phtml'; } } \ No newline at end of file diff --git a/src/app/controller/user.php b/src/app/controller/user.php index ba58cdb..0f6b883 100644 --- a/src/app/controller/user.php +++ b/src/app/controller/user.php @@ -100,11 +100,36 @@ class AppControllerUser exit; } + public function getIdenticon(int $size) + { + $icon = new Jdenticon\Identicon(); + + $icon->setValue($this->_user->public ? $this->_user->address : $this->_user->userId); + $icon->setSize($size); + $icon->setStyle( + [ + 'backgroundColor' => 'rgba(255, 255, 255, 0)', + ] + ); + + return $icon->getImageDataUri('webp'); + } + public function getUser() { return $this->_user; } + public function getPublic() + { + return $this->_user->public; + } + + public function getAddress() + { + return $this->_user->address; + } + public function findUserPageStarsDistinctTotalByValue(bool $value) : int { return $this->_database->findUserPageStarsDistinctTotal( diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml index b102482..42c70dc 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/src/app/view/theme/default/module/profile.phtml @@ -1,4 +1,11 @@ -
+
+
+ + identicon + + identicon + +
diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index cd88737..846cdeb 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -38,6 +38,9 @@ if (!file_exists(__DIR__ . '/env.' . PHP_ENV . '.php')) // Load environment require_once __DIR__ . '/env.' . PHP_ENV . '.php'; +// Autoload vendors +require_once __DIR__ . '/../../vendor/autoload.php'; + // Route parse_str($_SERVER['QUERY_STRING'], $request); diff --git a/src/public/assets/theme/default/css/framework.css b/src/public/assets/theme/default/css/framework.css index 4f5968b..420b599 100644 --- a/src/public/assets/theme/default/css/framework.css +++ b/src/public/assets/theme/default/css/framework.css @@ -108,6 +108,10 @@ a.label-green:hover { position: fixed; } +.vertical-align-middle { + vertical-align: middle; +} + .top--2 { top: -2px; } @@ -124,14 +128,22 @@ a.label-green:hover { border-radius: 3px; } +.border-radius-50 { + border-radius: 50%; +} + .border-pink-light { border: 1px #9b6895 solid; } -.border-pink { +.border-color-pink { border: 1px #a44399 solid; } +.border-color-green { + border: 1px #65916d solid; +} + .border-bottom-pink { border-bottom: 1px #a44399 solid; } @@ -148,6 +160,10 @@ a.label-green:hover { border-top: 1px #5d627d solid; } +.border-width-2 { + border-width: 2px; +} + .background-color-night { background-color: #34384f; } @@ -314,6 +330,11 @@ a:visited.background-color-hover-night-light:hover { margin-bottom: 8px; } +.margin-y-16 { + margin-top: 16px; + margin-bottom: 16px; +} + .margin-t-16 { margin-top: 16px; } From 919bc0a66c1f5cb1391bf40f2defde47341e222d Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 02:25:36 +0300 Subject: [PATCH 012/434] change block size --- src/app/view/theme/default/module/profile.phtml | 2 +- src/public/assets/theme/default/css/framework.css | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml index 42c70dc..b8c4245 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/src/app/view/theme/default/module/profile.phtml @@ -1,4 +1,4 @@ -
+
identicon diff --git a/src/public/assets/theme/default/css/framework.css b/src/public/assets/theme/default/css/framework.css index 420b599..967b2aa 100644 --- a/src/public/assets/theme/default/css/framework.css +++ b/src/public/assets/theme/default/css/framework.css @@ -313,8 +313,8 @@ a:visited.background-color-hover-night-light:hover { margin-left: 12px; } -.margin-l--176 { - margin-left: -176px; +.margin-l--196 { + margin-left: -196px; } .margin-y-8 { @@ -399,8 +399,8 @@ a:visited.background-color-hover-night-light:hover { width: 13px; } -.width-160-px { - width: 160px; +.width-180-px { + width: 180px; } @media (max-width: 1220px) { From 9b0bfcb76cd272906de193e75fbcdab256ed7a31 Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 15:25:44 +0300 Subject: [PATCH 013/434] move validator to model, init separated config file in JSON format #14 --- .gitignore | 3 +- .../valid.php => app/model/validator.php} | 189 ++++++++++++------ src/config/bootstrap.php | 14 +- src/config/validator.json | 37 ++++ 4 files changed, 181 insertions(+), 62 deletions(-) rename src/{library/valid.php => app/model/validator.php} (85%) create mode 100644 src/config/validator.json diff --git a/.gitignore b/.gitignore index 6d2670d..afb0638 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,8 @@ /src/public/api/*.json /src/config/* -!/src/config/bootstrap.json +!/src/config/bootstrap.php +!/src/config/validator.json !/src/config/nodes.json !/src/config/trackers.json !/src/config/peers.json diff --git a/src/library/valid.php b/src/app/model/validator.php similarity index 85% rename from src/library/valid.php rename to src/app/model/validator.php index 0c3b843..dfb99f7 100644 --- a/src/library/valid.php +++ b/src/app/model/validator.php @@ -1,9 +1,78 @@ _config = $config; + } + + // Page + + /// Page title + public function getPageTitleLengthMin() : int + { + return $this->_config->page->title->length->min; + } + + public function getPageTitleLengthMax() : int + { + return $this->_config->page->title->length->max; + } + + public function getPageTitleRegex() : string + { + return $this->_config->page->title->regex; + } + + /// Page description + public function getPageDescriptionLengthMin() : int + { + return $this->_config->page->description->length->min; + } + + public function getPageDescriptionLengthMax() : int + { + return $this->_config->page->description->length->max; + } + + public function getPageDescriptionRegex() : string + { + return $this->_config->page->description->regex; + } + + /// Page keywords + public function getPageKeywordsLengthMin() : int + { + return $this->_config->page->keywords->length->min; + } + + public function getPageKeywordsLengthMax() : int + { + return $this->_config->page->keywords->length->max; + } + + public function getPageKeywordsQuantityMin() : int + { + return $this->_config->page->keywords->quantity->min; + } + + public function getPageKeywordsQuantityMax() : int + { + return $this->_config->page->keywords->quantity->max; + } + + public function getPageKeywordsRegex() : string + { + return $this->_config->page->keywords->regex; + } + // Common - public static function host(mixed $value, array &$error = []) : bool + public function host(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -45,7 +114,7 @@ class Valid return true; } - public static function url(mixed $value, array &$error = []) : bool + public function url(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -101,7 +170,7 @@ class Valid } // User - public static function user(mixed $value, array &$error = []) : bool + public function user(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -133,7 +202,7 @@ class Valid return true; } - public static function userId(mixed $value, array &$error = []) : bool + public function userId(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -148,7 +217,7 @@ class Valid return true; } - public static function userAddress(mixed $value, array &$error = []) : bool + public function userAddress(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -176,7 +245,7 @@ class Valid return true; } - public static function userTimeAdded(mixed $value, array &$error = []) : bool + public function userTimeAdded(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -201,7 +270,7 @@ class Valid return true; } - public static function userTimeUpdated(mixed $value, array &$error = []) : bool + public function userTimeUpdated(mixed $value, array &$error = []) : bool { if (!(is_int($value) || is_bool($value))) { @@ -226,7 +295,7 @@ class Valid return true; } - public static function userApproved(mixed $value, array &$error = []) : bool + public function userApproved(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -241,7 +310,7 @@ class Valid return true; } - public static function userPublic(mixed $value, array &$error = []) : bool + public function userPublic(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -257,7 +326,7 @@ class Valid } // Magnet - public static function magnet(mixed $value, array &$error = []) : bool + public function magnet(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -309,7 +378,7 @@ class Valid return true; } - public static function magnetId(mixed $value, array &$error = []) : bool + public function magnetId(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -324,7 +393,7 @@ class Valid return true; } - public static function magnetTitle(mixed $value, array &$error = []) : bool + public function magnetTitle(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -367,7 +436,7 @@ class Valid return true; } - public static function magnetPreview(mixed $value, array &$error = []) : bool + public function magnetPreview(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -410,7 +479,7 @@ class Valid return true; } - public static function magnetDescription(mixed $value, array &$error = []) : bool + public function magnetDescription(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -453,7 +522,7 @@ class Valid return true; } - public static function magnetComments(mixed $value, array &$error = []) : bool + public function magnetComments(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -468,7 +537,7 @@ class Valid return true; } - public static function magnetPublic(mixed $value, array &$error = []) : bool + public function magnetPublic(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -483,7 +552,7 @@ class Valid return true; } - public static function magnetApproved(mixed $value, array &$error = []) : bool + public function magnetApproved(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -498,7 +567,7 @@ class Valid return true; } - public static function magnetSensitive(mixed $value, array &$error = []) : bool + public function magnetSensitive(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -513,7 +582,7 @@ class Valid return true; } - public static function magnetTimeAdded(mixed $value, array &$error = []) : bool + public function magnetTimeAdded(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -538,7 +607,7 @@ class Valid return true; } - public static function magnetTimeUpdated(mixed $value, array &$error = []) : bool + public function magnetTimeUpdated(mixed $value, array &$error = []) : bool { if (!(is_int($value) || is_bool($value))) { @@ -563,7 +632,7 @@ class Valid return true; } - public static function magnetDn(mixed $value, array &$error = []) : bool + public function magnetDn(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -606,7 +675,7 @@ class Valid return true; } - public static function magnetXl(mixed $value, array &$error = []) : bool + public function magnetXl(mixed $value, array &$error = []) : bool { if (!(is_int($value) || is_float($value))) { @@ -621,7 +690,7 @@ class Valid return true; } - public static function magnetKt(mixed $value, array &$error = []) : bool + public function magnetKt(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -696,7 +765,7 @@ class Valid return true; } - public static function magnetXt(mixed $value, array &$error = []) : bool + public function magnetXt(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -794,7 +863,7 @@ class Valid return true; } - public static function magnetTr(mixed $value, array &$error = []) : bool + public function magnetTr(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -844,7 +913,7 @@ class Valid return true; } - public static function magnetAs(mixed $value, array &$error = []) : bool + public function magnetAs(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -894,7 +963,7 @@ class Valid return true; } - public static function magnetWs(mixed $value, array &$error = []) : bool + public function magnetWs(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -945,7 +1014,7 @@ class Valid } // Magnet comment - public static function magnetComment(mixed $value, array &$error = []) : bool + public function magnetComment(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -979,7 +1048,7 @@ class Valid return true; } - public static function magnetCommentId(mixed $value, array &$error = []) : bool + public function magnetCommentId(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -994,7 +1063,7 @@ class Valid return true; } - public static function magnetCommentIdParent(mixed $value, array &$error = []) : bool + public function magnetCommentIdParent(mixed $value, array &$error = []) : bool { if (!(is_null($value) || is_int($value))) { @@ -1014,7 +1083,7 @@ class Valid return true; } - public static function magnetCommentTimeAdded(mixed $value, array &$error = []) : bool + public function magnetCommentTimeAdded(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1039,7 +1108,7 @@ class Valid return true; } - public static function magnetCommentApproved(mixed $value, array &$error = []) : bool + public function magnetCommentApproved(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -1054,7 +1123,7 @@ class Valid return true; } - public static function magnetCommentPublic(mixed $value, array &$error = []) : bool + public function magnetCommentPublic(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -1069,7 +1138,7 @@ class Valid return true; } - public static function magnetCommentValue(mixed $value, array &$error = []) : bool + public function magnetCommentValue(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -1100,7 +1169,7 @@ class Valid } // Magnet download - public static function magnetDownload(mixed $value, array &$error = []) : bool + public function magnetDownload(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -1129,7 +1198,7 @@ class Valid return true; } - public static function magnetDownloadId(mixed $value, array &$error = []) : bool + public function magnetDownloadId(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1144,7 +1213,7 @@ class Valid return true; } - public static function magnetDownloadTimeAdded(mixed $value, array &$error = []) : bool + public function magnetDownloadTimeAdded(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1170,7 +1239,7 @@ class Valid } // Magnet star - public static function magnetStar(mixed $value, array &$error = []) : bool + public function magnetStar(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -1200,7 +1269,7 @@ class Valid return true; } - public static function magnetStarId(mixed $value, array &$error = []) : bool + public function magnetStarId(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1215,7 +1284,7 @@ class Valid return true; } - public static function magnetStarValue(mixed $value, array &$error = []) : bool + public function magnetStarValue(mixed $value, array &$error = []) : bool { if (!is_bool($value)) { @@ -1230,7 +1299,7 @@ class Valid return true; } - public static function magnetStarTimeAdded(mixed $value, array &$error = []) : bool + public function magnetStarTimeAdded(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1256,7 +1325,7 @@ class Valid } // Magnet view - public static function magnetView(mixed $value, array &$error = []) : bool + public function magnetView(mixed $value, array &$error = []) : bool { if (!is_object($value)) { @@ -1285,7 +1354,7 @@ class Valid return true; } - public static function magnetViewId(mixed $value, array &$error = []) : bool + public function magnetViewId(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1300,7 +1369,7 @@ class Valid return true; } - public static function magnetViewTimeAdded(mixed $value, array &$error = []) : bool + public function magnetViewTimeAdded(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1326,7 +1395,7 @@ class Valid } // Torrent - public static function torrentAnnounce(mixed $value, array &$error = []) : bool + public function torrentAnnounce(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -1354,7 +1423,7 @@ class Valid return true; } - public static function torrentAnnounceList(mixed $value, array &$error = []) : bool + public function torrentAnnounceList(mixed $value, array &$error = []) : bool { if (!is_array($value)) { @@ -1417,7 +1486,7 @@ class Valid return true; } - public static function torrentComment(mixed $value, array &$error = []) : bool + public function torrentComment(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -1460,7 +1529,7 @@ class Valid return true; } - public static function torrentCreatedBy(mixed $value, array &$error = []) : bool + public function torrentCreatedBy(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -1503,7 +1572,7 @@ class Valid return true; } - public static function torrentCreationDate(mixed $value, array &$error = []) : bool + public function torrentCreationDate(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1528,7 +1597,7 @@ class Valid return true; } - public static function torrentInfo(mixed $value, array &$error = []) : bool + public function torrentInfo(mixed $value, array &$error = []) : bool { if (!is_array($value)) { @@ -1705,7 +1774,7 @@ class Valid return true; } - public static function torrentInfoName(mixed $value, array &$error = []) : bool + public function torrentInfoName(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -1748,7 +1817,7 @@ class Valid return true; } - public static function torrentInfoSource(mixed $value, array &$error = []) : bool + public function torrentInfoSource(mixed $value, array &$error = []) : bool { if (!is_string($value)) { @@ -1793,7 +1862,7 @@ class Valid return true; } - public static function torrentInfoFileDuration(mixed $value, array &$error = []) : bool + public function torrentInfoFileDuration(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1818,7 +1887,7 @@ class Valid return true; } - public static function torrentInfoPieceLength(mixed $value, array &$error = []) : bool + public function torrentInfoPieceLength(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1843,14 +1912,14 @@ class Valid return true; } - public static function torrentInfoPieces(mixed $value, array &$error = []) : bool + public function torrentInfoPieces(mixed $value, array &$error = []) : bool { // @TODO return true; } - public static function torrentInfoPrivate(mixed $value, array &$error = []) : bool + public function torrentInfoPrivate(mixed $value, array &$error = []) : bool { if (!is_int($value)) { @@ -1875,21 +1944,21 @@ class Valid return true; } - public static function torrentInfoProfiles(mixed $value, array &$error = []) : bool + public function torrentInfoProfiles(mixed $value, array &$error = []) : bool { // @TODO return true; } - public static function torrentInfoFileMedia(mixed $value, array &$error = []) : bool + public function torrentInfoFileMedia(mixed $value, array &$error = []) : bool { // @TODO return true; } - public static function torrentInfoFiles(mixed $value, array &$error = []) : bool + public function torrentInfoFiles(mixed $value, array &$error = []) : bool { // @TODO diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index 846cdeb..22f72b8 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -98,9 +98,21 @@ if (isset($request['_route_'])) case 'submit': + require_once(__DIR__ . '/../app/model/validator.php'); + + $validator = new AppModelValidator( + json_decode( + file_get_contents( + __DIR__ . '/../config/validator.json' + ) + ) + ); + require_once(__DIR__ . '/../app/controller/submit.php'); - $controller = new AppControllerSubmit(); + $controller = new AppControllerSubmit( + $validator + ); break; diff --git a/src/config/validator.json b/src/config/validator.json new file mode 100644 index 0000000..55dd55f --- /dev/null +++ b/src/config/validator.json @@ -0,0 +1,37 @@ +{ + "page": + { + "title": + { + "length": + { + "min": 10, + "max": 255 + }, + "regex": "/.*/ui" + }, + "description": + { + "length": + { + "min": 0, + "max": 10000 + }, + "regex": "/.*/ui" + }, + "keywords": + { + "quantity": + { + "min": 0, + "max": 20 + }, + "length": + { + "min": 0, + "max": 140 + }, + "regex": "/[\\w]+/ui" + } + } +} From 4697519663f10872c50d52010a83091b200dd23f Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 15:54:38 +0300 Subject: [PATCH 014/434] change search form placeholder #14 --- src/app/view/theme/default/module/search.phtml | 4 ++-- src/public/assets/theme/default/css/framework.css | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/app/view/theme/default/module/search.phtml b/src/app/view/theme/default/module/search.phtml index 540e606..622292b 100644 --- a/src/app/view/theme/default/module/search.phtml +++ b/src/app/view/theme/default/module/search.phtml @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/public/assets/theme/default/css/framework.css b/src/public/assets/theme/default/css/framework.css index 967b2aa..0c3cb70 100644 --- a/src/public/assets/theme/default/css/framework.css +++ b/src/public/assets/theme/default/css/framework.css @@ -100,6 +100,16 @@ a.label-green:hover { background-color: #65916d; } +.button-green { + color: #fff; + background-color: #65916d; +} + +.button-green:hover { + color: #fff; + background-color: #709e79; +} + .position-relative { position: relative; } @@ -403,6 +413,10 @@ a:visited.background-color-hover-night-light:hover { width: 180px; } +.min-width-200-px { + width: 200px; +} + @media (max-width: 1220px) { .width-tablet-100 { From 31b8a0a5fbc765cfd8d8e3ae688b28d0bad514de Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 15:57:08 +0300 Subject: [PATCH 015/434] draft submit form #14 --- src/app/controller/submit.php | 80 +++++++++++++++++++ src/app/view/theme/default/submit.phtml | 102 ++++++++++++++++++++++-- 2 files changed, 175 insertions(+), 7 deletions(-) diff --git a/src/app/controller/submit.php b/src/app/controller/submit.php index dddf4fc..cc13f71 100644 --- a/src/app/controller/submit.php +++ b/src/app/controller/submit.php @@ -2,6 +2,13 @@ class AppControllerSubmit { + private $_validator; + + public function __construct(AppModelValidator $validator) + { + $this->_validator = $validator; + } + private function _response(string $title, string $h1, string $text, int $code = 200) { require_once __DIR__ . '/response.php'; @@ -48,6 +55,79 @@ class AppControllerSubmit ); } + // Form + $form = (object) + [ + 'title' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'minlength' => $this->_validator->getPageTitleLengthMin(), + 'maxlength' => $this->_validator->getPageTitleLengthMax(), + 'placeholder' => sprintf( + _('Page subject (%s-%s chars)'), + number_format($this->_validator->getPageTitleLengthMin()), + number_format($this->_validator->getPageTitleLengthMax()) + ), + ] + ], + 'description' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'minlength' => $this->_validator->getPageDescriptionLengthMin(), + 'maxlength' => $this->_validator->getPageDescriptionLengthMax(), + 'placeholder' => sprintf( + _('Page description text (%s-%s chars)'), + number_format($this->_validator->getPageDescriptionLengthMin()), + number_format($this->_validator->getPageDescriptionLengthMax()) + ), + ] + ], + 'keywords' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'minlength' => $this->_validator->getPageKeywordsLengthMin(), + 'maxlength' => $this->_validator->getPageKeywordsLengthMax(), + 'placeholder' => sprintf( + _('Page keywords (%s-%s total / %s-%s chars per item)'), + number_format($this->_validator->getPageKeywordsQuantityMin()), + number_format($this->_validator->getPageKeywordsQuantityMax()), + number_format($this->_validator->getPageKeywordsLengthMin()), + number_format($this->_validator->getPageKeywordsLengthMax()) + ), + ] + ], + 'torrent' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'placeholder' => sprintf( + _('Torrent file (use Ctrl to select multiple files)') + ), + ], + ], + 'magnet' => (object) + [ + 'error' => [], + 'value' => null, + ], + ]; + + if (isset($_POST)) + { + + } + // Render require_once __DIR__ . '/module/head.php'; diff --git a/src/app/view/theme/default/submit.phtml b/src/app/view/theme/default/submit.phtml index 9b28efa..2b250d8 100644 --- a/src/app/view/theme/default/submit.phtml +++ b/src/app/view/theme/default/submit.phtml @@ -12,17 +12,105 @@

-
+
+ + + + + + + title->error as $error) { ?> +
+ +
+ + +
+
+ + + + + + + description->error as $error) { ?> +
+ +
+ + +
+
+ + + + + + + keywords->error as $error) { ?> +
+ +
+ + +
+
+ + + + + + + torrent->error as $error) { ?> +
+ +
+ + +
+ +
+
From 762d72e913a149ebc05718801bdcf3d3f31ad255 Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 15:59:01 +0300 Subject: [PATCH 016/434] change menu order #15 --- .../view/theme/default/module/profile.phtml | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml index b8c4245..29227bd 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/src/app/view/theme/default/module/profile.phtml @@ -1,4 +1,5 @@
+ @@ -25,25 +27,6 @@ - - - - - - - - - - - - - - - - - - - @@ -169,4 +152,23 @@ + + + + + + + + + + + + + + + + + + +
\ No newline at end of file From d8f6b6d27eda0338dca4884e8c52837fa8215d91 Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 16:36:43 +0300 Subject: [PATCH 017/434] add required fields configuration, draft post request processing #14 --- src/app/controller/submit.php | 47 +++++ src/app/model/validator.php | 256 ++++++++++++++++++++++++ src/app/view/theme/default/submit.phtml | 5 +- src/config/validator.json | 12 ++ 4 files changed, 319 insertions(+), 1 deletion(-) diff --git a/src/app/controller/submit.php b/src/app/controller/submit.php index cc13f71..d72840a 100644 --- a/src/app/controller/submit.php +++ b/src/app/controller/submit.php @@ -64,6 +64,7 @@ class AppControllerSubmit 'attribute' => (object) [ 'value' => null, + 'required' => $this->_validator->getPageTitleRequired(), 'minlength' => $this->_validator->getPageTitleLengthMin(), 'maxlength' => $this->_validator->getPageTitleLengthMax(), 'placeholder' => sprintf( @@ -79,6 +80,7 @@ class AppControllerSubmit 'attribute' => (object) [ 'value' => null, + 'required' => $this->_validator->getPageDescriptionRequired(), 'minlength' => $this->_validator->getPageDescriptionLengthMin(), 'maxlength' => $this->_validator->getPageDescriptionLengthMax(), 'placeholder' => sprintf( @@ -94,6 +96,7 @@ class AppControllerSubmit 'attribute' => (object) [ 'value' => null, + 'required' => $this->_validator->getPageKeywordsRequired(), 'minlength' => $this->_validator->getPageKeywordsLengthMin(), 'maxlength' => $this->_validator->getPageKeywordsLengthMax(), 'placeholder' => sprintf( @@ -111,6 +114,7 @@ class AppControllerSubmit 'attribute' => (object) [ 'value' => null, + 'required' => $this->_validator->getPageTorrentRequired(), 'placeholder' => sprintf( _('Torrent file (use Ctrl to select multiple files)') ), @@ -123,9 +127,52 @@ class AppControllerSubmit ], ]; + // Submit request if (isset($_POST)) { + if (isset($_POST['title'])) + { + $error = []; + if ($this->_validator->pageTitle($_POST['title'], $error)) + { + $form->title->error = $error; + } + + // @TODO check for page duplicates + + $form->title->attribute->value = htmlentities($_POST['title']); + } + + if (isset($_POST['description'])) + { + $error = []; + + if ($this->_validator->pageDescription($_POST['description'], $error)) + { + $form->description->error = $error; + } + + $form->description->attribute->value = htmlentities($_POST['description']); + } + + if (isset($_POST['keywords'])) + { + $error = []; + + if ($this->_validator->pageKeywords($_POST['keywords'], $error)) + { + $form->keywords->error = $error; + } + + $form->keywords->attribute->value = htmlentities($_POST['keywords']); + } + + // Request valid + if (empty($error)) + { + // @TODO redirect + } } // Render diff --git a/src/app/model/validator.php b/src/app/model/validator.php index dfb99f7..8b3da69 100644 --- a/src/app/model/validator.php +++ b/src/app/model/validator.php @@ -14,6 +14,11 @@ class AppModelValidator // Page /// Page title + public function getPageTitleRequired() : bool + { + return $this->_config->page->title->required; + } + public function getPageTitleLengthMin() : int { return $this->_config->page->title->length->min; @@ -29,7 +34,65 @@ class AppModelValidator return $this->_config->page->title->regex; } + public function pageTitle(mixed $value, array &$error = []) : bool + { + if (!is_string($value)) + { + array_push( + $error, + _('Invalid page title data type') + ); + + return false; + } + + if (empty($value) && $this->getPageTitleRequired()) + { + array_push( + $error, + _('Page title required') + ); + + return false; + } + + if (!preg_match($this->getPageTitleRegex(), $value)) + { + array_push( + $error, + sprintf( + _('Page title format does not match condition "%s"'), + $this->getPageTitleRegex() + ) + ); + + return false; + } + + if (mb_strlen($value) < $this->getPageTitleLengthMin() || + mb_strlen($value) > $this->getPageTitleLengthMax()) + { + array_push( + $error, + sprintf( + _('Page title out of %s-%s chars range'), + $this->getPageTitleLengthMin(), + $this->getPageTitleLengthMax() + ) + ); + + return false; + } + + return true; + } + /// Page description + public function getPageDescriptionRequired() : bool + { + return $this->_config->page->description->required; + } + public function getPageDescriptionLengthMin() : int { return $this->_config->page->description->length->min; @@ -45,7 +108,65 @@ class AppModelValidator return $this->_config->page->description->regex; } + public function pageDescription(mixed $value, array &$error = []) : bool + { + if (!is_string($value)) + { + array_push( + $error, + _('Invalid page description data type') + ); + + return false; + } + + if (empty($value) && $this->getPageDescriptionRequired()) + { + array_push( + $error, + _('Page description required') + ); + + return false; + } + + if (!preg_match($this->getPageDescriptionRegex(), $value)) + { + array_push( + $error, + sprintf( + _('Page description format does not match condition "%s"'), + $this->getPageDescriptionRegex() + ) + ); + + return false; + } + + if (mb_strlen($value) < $this->getPageDescriptionLengthMin() || + mb_strlen($value) > $this->getPageDescriptionLengthMax()) + { + array_push( + $error, + sprintf( + _('Page description out of %s-%s chars range'), + $this->getPageDescriptionLengthMin(), + $this->getPageDescriptionLengthMax() + ) + ); + + return false; + } + + return true; + } + /// Page keywords + public function getPageKeywordsRequired() : bool + { + return $this->_config->page->keywords->required; + } + public function getPageKeywordsLengthMin() : int { return $this->_config->page->keywords->length->min; @@ -71,6 +192,141 @@ class AppModelValidator return $this->_config->page->keywords->regex; } + public function pageKeywords(mixed $value, array &$error = []) : bool + { + if (!is_array($value)) + { + array_push( + $error, + _('Invalid page keywords data type') + ); + + return false; + } + + if (empty($value) && $this->getPageKeywordsRequired()) + { + array_push( + $error, + _('Page keywords required') + ); + + return false; + } + + $total = 0; + + foreach ($value as $keywords) + { + if (!is_string($kt)) + { + array_push( + $error, + _('Invalid magnet keyword value data type') + ); + + return false; + } + + if (!preg_match(MAGNET_KT_REGEX, $kt)) + { + array_push( + $error, + sprintf( + _('Magnet keyword format does not match condition "%s"'), + MAGNET_KT_REGEX + ) + ); + + return false; + } + + if (mb_strlen($kt) < MAGNET_KT_MIN_LENGTH || + mb_strlen($kt) > MAGNET_KT_MAX_LENGTH) + { + array_push( + $error, + sprintf( + _('Magnet keyword out of %s-%s chars range'), + MAGNET_KT_MIN_LENGTH, + MAGNET_KT_MAX_LENGTH + ) + ); + + return false; + } + + $total++; + } + + if ($total < MAGNET_KT_MIN_QUANTITY || + $total > MAGNET_KT_MAX_QUANTITY) + { + array_push( + $error, + sprintf( + _('Magnet keywords quantity out of %s-%s range'), + MAGNET_KT_MIN_QUANTITY, + MAGNET_KT_MAX_QUANTITY + ) + ); + + return false; + } + + return true; + } + + public function pageKeyword(mixed $value, array &$error = []) : bool + { + if (!is_string($value)) + { + array_push( + $error, + _('Invalid page keyword data type') + ); + + return false; + } + + if (!preg_match($this->getPageKeywordsRegex(), $value)) + { + array_push( + $error, + sprintf( + _('Page keyword "%s" format does not match condition "%s"'), + $value, + $this->getPageKeywordsRegex() + ) + ); + + return false; + } + + if (mb_strlen($value) < $this->getPageDescriptionLengthMin() || + mb_strlen($value) > $this->getPageDescriptionLengthMax()) + { + array_push( + $error, + sprintf( + _('Page description out of %s-%s chars range'), + $this->getPageDescriptionLengthMin(), + $this->getPageDescriptionLengthMax() + ) + ); + + return false; + } + + return true; + } + + /// Page torrent + public function getPageTorrentRequired() : bool + { + return $this->_config->page->torrent->required; + } + // Common public function host(mixed $value, array &$error = []) : bool { diff --git a/src/app/view/theme/default/submit.phtml b/src/app/view/theme/default/submit.phtml index 2b250d8..6a16a49 100644 --- a/src/app/view/theme/default/submit.phtml +++ b/src/app/view/theme/default/submit.phtml @@ -32,6 +32,7 @@ type="text" name="title" id="title" + title->attribute->required ? 'required="required"' : false ?> value="title->attribute->value ?>" placeholder="title->attribute->placeholder ?>" minlength="title->attribute->minlength ?>" @@ -55,6 +56,7 @@ @@ -76,7 +78,7 @@ @@ -101,6 +103,7 @@ multiple="multiple" name="torrent" id="torrent" + torrent->attribute->required ? 'required="required"' : false ?> value="torrent->attribute->value ?>" />
-
-
-

-
-
- - - - -
-
- - - - form->xt->value[1]) && empty($response->form->xt->value[2])) { ?> -
- - - - - - -
-
- -
- -
-
- -
-
- -
- address, MODERATOR_IP_LIST)) { ?> -
- -
- -
-
- -
-
-
- -
-
message ?>
-
- -
-
- - -
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - \ No newline at end of file diff --git a/src/public/faq.php b/src/public/faq.php deleted file mode 100644 index 06000d9..0000000 --- a/src/public/faq.php +++ /dev/null @@ -1,228 +0,0 @@ - - true, - 'message' => _('Internal server error'), -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required for this action'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Get user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not init user info'); -} - -// On first visit, redirect user to the welcome page with access level question -/* Allow to users read this page before accepting data access type in welcome form -else if (is_null($user->public)) -{ - header( - sprintf('Location: %s/welcome.php', WEBSITE_URL) - ); -} -*/ - -?> - - - - - - - <?php echo sprintf(_('F.A.Q. - %s'), WEBSITE_NAME) ?> - - - - - -
-
- -
-
-
-
-
-
-
- success) { ?> -

-
- -
-
-

- - - - - - -
-

open source community-driven BitTorrent registry for Yggdrasil ecosystem.') ?>

-

-
-
-
-

- - - - - - -
-

-

-

-
-
-
-

- - - - - - -
-

Node page.'), WEBSITE_URL) ?>

-
-
-
- -
-
-

- - - - - - -
-

Submit.') ?>

-

-

-
-
-
-

- - - - - - -
-

-

qBittorrent supports all required features, just check Preferences - Advanced - Optional IP address to bind and set to All addresses or Yggdrasil address only.') ?>

-

') ?>

-
-
-
-

- - - - - - -
-

-

-

-
-
-
- -
-
-

- - - - - - -
-

-

Install section.') ?>

-

trackers.json registry to participate shared model testing.') ?>

-
-
-
-

- - - - - - -
-

Issues page!') ?>

-
-
- -
message ?>
- -
-
-
-
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - \ No newline at end of file diff --git a/src/public/magnet.php b/src/public/magnet.php deleted file mode 100644 index 2c3f1e0..0000000 --- a/src/public/magnet.php +++ /dev/null @@ -1,508 +0,0 @@ - true, - 'message' => false, - 'magnet' => [], - 'comments' => [], -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required to enable resource features'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Get user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not init user info'); -} - -// Init magnet -else if (!$magnet = $db->getMagnet(isset($_GET['magnetId']) ? (int) $_GET['magnetId'] : 0)) -{ - $response->success = false; - $response->message = _('Magnet not found! Submit new magnet link by sending address to the search field.'); -} - -// On first visit, redirect user to the welcome page with access level question -else if (is_null($user->public) && !isset($_GET['rss'])) -{ - header( - sprintf('Location: %s/welcome.php', WEBSITE_URL) - ); -} - -// Request valid -else -{ - // Get access info - $accessRead = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved)); - $accessEdit = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST)); - - // Update magnet viewed - if ($accessRead) - { - if ($magnetViewId = $db->addMagnetView($magnet->magnetId, $userId, time())) - { - // Push event to other nodes - if (API_EXPORT_ENABLED && - API_EXPORT_PUSH_ENABLED && - API_EXPORT_USERS_ENABLED && - API_EXPORT_MAGNETS_ENABLED && - API_EXPORT_MAGNET_VIEWS_ENABLED) - { - if (!$memoryApiExportPush = $memory->get('api.export.push')) - { - $memoryApiExportPush = []; - } - - $memoryApiExportPush[] = (object) - [ - 'time' => time(), - 'userId' => $user->userId, - 'magnetId' => $magnet->magnetId, - 'magnetViewId' => $magnetViewId - ]; - - $memory->set('api.export.push', $memoryApiExportPush, 3600); - } - } - } - - // Keywords - $keywords = []; - - foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $keyword) - { - $keywords[] = $db->getKeywordTopic($keyword->keywordTopicId)->value; - } - - $response->user = $user; - $response->magnet = (object) - [ - 'magnetId' => $magnet->magnetId, - 'title' => $magnet->title ? htmlentities($magnet->title) : ($magnet->dn ? htmlentities($magnet->dn): false), - 'preview' => $magnet->preview ? nl2br( - htmlentities( - $magnet->preview - ) - ) : false, - 'description' => $magnet->description ? nl2br( - htmlentities( - $magnet->description - ) - ) : false, - 'approved' => (bool) $magnet->approved, - 'public' => (bool) $magnet->public, - 'sensitive' => (bool) $magnet->sensitive, - 'comments' => (bool) $magnet->comments, - 'timeAdded' => $magnet->timeAdded ? Time::ago((int) $magnet->timeAdded) : false, - 'timeUpdated' => $magnet->timeUpdated ? Time::ago((int) $magnet->timeUpdated) : false, - 'keywords' => $keywords, - 'comment' => (object) - [ - 'total' => $db->findMagnetCommentsTotalByMagnetId($magnet->magnetId), - 'status' => $db->findMagnetCommentsTotal($magnet->magnetId, $userId), - ], - 'download' => (object) - [ - 'total' => $db->findMagnetDownloadsTotalByMagnetId($magnet->magnetId), - 'status' => $db->findMagnetDownloadsTotal($magnet->magnetId, $userId), - ], - 'star' => (object) - [ - 'total' => $db->findMagnetStarsTotalByMagnetId($magnet->magnetId, true), - 'status' => $db->findLastMagnetStarValue($magnet->magnetId, $userId), - ], - 'access' => (object) - [ - 'read' => $accessRead, - 'edit' => $accessEdit, - ], - 'seeders' => $db->getMagnetToAddressTrackerSeedersSumByMagnetId($magnet->magnetId), - 'completed' => $db->getMagnetToAddressTrackerCompletedSumByMagnetId($magnet->magnetId), - 'leechers' => $db->getMagnetToAddressTrackerLeechersSumByMagnetId($magnet->magnetId), - 'directs' => $db->getMagnetToAcceptableSourceTotalByMagnetId($magnet->magnetId), - ]; -} - -if (isset($_GET['rss']) && isset($_GET['target']) && $_GET['target'] == 'comment' && $response->success) { ?>' . PHP_EOL ?> - - - - magnet->magnetId) ?> - <?php echo sprintf(_('%s - Comments - %s'), htmlentities($response->magnet->title), WEBSITE_NAME) ?> - - findMagnetComments($response->magnet->magnetId) as $magnetComment) { ?> - user->address == $db->getUser($magnetComment->userId)->address || in_array($response->user->address, MODERATOR_IP_LIST)) { ?> - - <?php echo sprintf('%s - comment #%s', htmlspecialchars($magnet->title, ENT_QUOTES, 'UTF-8'), $magnetComment->magnetCommentId) ?> - value, ENT_QUOTES, 'UTF-8') ?> - magnet->magnetId, $magnetComment->magnetCommentId) ?> - magnet->magnetId, $magnetComment->magnetCommentId) ?> - - - - - - - - - - - - success) { ?> - <?php echo sprintf(_('%s - %s'), htmlentities($response->magnet->title), WEBSITE_NAME) ?> - - - - <?php echo $response->message ?> - - - - - -
-
- -
-
-
-
-
-
- success) { ?> - magnet->access->read) { ?> -
-
- -

magnet->title ?>

- magnet->leechers && !$response->magnet->seeders) { ?> - - - - -
- magnet->public) { ?> - - - - - - - - magnet->approved) { ?> - - - - - - - magnet->access->edit) { ?> - - - - - - - - - -
- magnet->preview) { ?> -
magnet->preview ?>
- - magnet->description) { ?> -
magnet->description ?>
- - magnet->keywords) { ?> -
- magnet->keywords as $keyword) { ?> - - # - - -
- -
- - - - magnet->timeUpdated ? _('Updated') : _('Added') ?> - magnet->timeUpdated ? $response->magnet->timeUpdated : $response->magnet->timeAdded ?> - - - - - - - magnet->seeders ?> - - - - - - magnet->completed ?> - - - - - - - magnet->leechers ?> - - magnet->directs) { ?> - - - - - magnet->directs ?> - - - - - magnet->star->status) { ?> - - - - - - - - - - magnet->star->total ?> - - magnet->comments) { ?> - - - magnet->comment->status) { ?> - - - - - - - - - - magnet->comment->total ?> - - - - - magnet->download->status) { ?> - - - - - - - - - - magnet->download->total ?> - -
-
- searchMagnetsTotal($magnet->title ? $magnet->title : $magnet->dn, 'similar', MAGNET_STOP_WORDS_SIMILAR)) { ?> - 1) { // skip current magnet ?> -
- -

-
-
-
- searchMagnets( - $magnet->title ? $magnet->title : $magnet->dn, - 0, - 10, - $similarMagnetsTotal, - 'similar', - MAGNET_STOP_WORDS_SIMILAR - ) as $result) { ?> - getMagnet($result->magnetid)) { ?> - magnetid != $response->magnet->magnetId && // skip current magnet - ($response->user->address == $db->getUser($magnet->userId)->address || - in_array($response->user->address, MODERATOR_IP_LIST) || ($magnet->approved && $magnet->public))) { ?> - - - - -
-
- - - magnet->comments) { ?> -
- -

- -
-
- findMagnetComments($response->magnet->magnetId) as $magnetComment) { ?> -
- - user->address == $db->getUser($magnetComment->userId)->address || - in_array($response->user->address, MODERATOR_IP_LIST) || - ($magnetComment->approved && $magnetComment->public)) { ?> -
- value)) ?> -
- - - - - timeAdded) ?> - - - public) { ?> - - - - - - - - approved) { ?> - - - - - - - - user->address, MODERATOR_IP_LIST)) { ?> - - approved) { ?> - - - - - - - - - -
- -
- -
-
- -
-
- -
-
-
- - -
-
-
- - -
-
message ?>
-
- -
-
-
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - - \ No newline at end of file diff --git a/src/public/node.php b/src/public/node.php deleted file mode 100644 index 08df253..0000000 --- a/src/public/node.php +++ /dev/null @@ -1,534 +0,0 @@ - - true, - 'message' => _('Internal server error'), -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required for this action'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Get user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not init user info'); -} - -// On first visit, redirect user to the welcome page with access level question -else if (is_null($user->public)) -{ - header( - sprintf('Location: %s/welcome.php', WEBSITE_URL) - ); -} - -?> - - - - - - - <?php echo sprintf(_('%s instance info'), WEBSITE_NAME) ?> - - - - - - -
-
- -
-
-
-
-
-
-
- success) { ?> -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $tracker) { ?> - - - - $value) { ?> - - - - - - - - - - - - - - - - $node) { ?> - - - - $value) { ?> - - - - - - - - - - - - - - - - $peer) { ?> - - - - $value) { ?> - - - - - - - - - - - - - - -
- -
- public ? _('Distributed') : _('Local') ?> - - - - - -
- address ?> -
- timeUpdated) ?> -
- -
- -
- getUsersTotal() ?> - getUsersTotalByPublic(true)) { ?> - / - - - - - getUsersTotalByPublic(false)) { ?> - / - - - - - - - - - -
- getMagnetsTotal() ?> - getMagnetsTotalByUsersPublic(true)) { ?> - / - - - - - getMagnetsTotalByUsersPublic(false)) { ?> - / - - - - - - - - - -
- getMagnetDownloadsTotal() ?> - findMagnetDownloadsTotalByUsersPublic(true)) { ?> - / - - - - - findMagnetDownloadsTotalByUsersPublic(false)) { ?> - / - - - - - - - - - -
- getMagnetCommentsTotal() ?> - findMagnetCommentsTotalByUsersPublic(true)) { ?> - / - - - - - findMagnetCommentsTotalByUsersPublic(false)) { ?> - / - - - - - - - - - -
- getMagnetStarsTotal() ?> - findMagnetStarsTotalByUsersPublic(true)) { ?> - / - - - - - findMagnetStarsTotalByUsersPublic(false)) { ?> - / - - - - - - - - - -
- getMagnetViewsTotal() ?> - findMagnetViewsTotalByUsersPublic(true)) { ?> - / - - - - - findMagnetViewsTotalByUsersPublic(false)) { ?> - / - - - - - - - - - -
getMagnetToAddressTrackerSeedersSum() ?>
getMagnetToAddressTrackerCompletedSum() ?>
getMagnetToAddressTrackerLeechersSum() ?>
- -
- -
-
-
-
- - - / - - -
- - -
- - -
- - -
- -
-
- - - - - - - - - -
- - - - - -
- - - - - - - - - -
- - - - - -
- - - - - - - - - -
- - - - - -
- -
message ?>
- -
-
-
-
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - | - - - | - -
-
-
-
- - \ No newline at end of file diff --git a/src/public/search.php b/src/public/search.php deleted file mode 100644 index ea1590c..0000000 --- a/src/public/search.php +++ /dev/null @@ -1,436 +0,0 @@ - false, - 'page' => 1, -]; - -// Prepare request -$request->query = isset($_GET['query']) ? urldecode((string) $_GET['query']) : ''; -$request->page = isset($_GET['page']) && $_GET['page'] > 0 ? (int) $_GET['page'] : 1; - -// Define response -$response = (object) -[ - 'success' => true, - 'message' => false, - 'magnets' => [], -]; - -// Yggdrasil connections only -if (!Valid::host($_SERVER['REMOTE_ADDR'])) -{ - $response->success = false; - $response->message = _('Yggdrasil connection required to enable resource features'); -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response->success = false; - $response->message = _('Could not init user session'); -} - -// Get user -else if (!$user = $db->getUser($userId)) -{ - $response->success = false; - $response->message = _('Could not init user info'); -} - -// On first visit, redirect user to the welcome page with access level question -else if (is_null($user->public) && !isset($_GET['rss'])) -{ - header( - sprintf('Location: %s/welcome.php', WEBSITE_URL) - ); -} - -// Request valid -else -{ - // Query is magnet link - if ($magnet = Yggverse\Parser\Magnet::is($request->query)) - { - header( - sprintf('Location: %s/action.php?target=magnet&toggle=new&magnet=%s', WEBSITE_URL, urlencode($request->query)) - ); - } - - // Get index - $response->total = $sphinx->searchMagnetsTotal($request->query); - $results = $sphinx->searchMagnets( - $request->query, - $request->page * WEBSITE_PAGINATION_LIMIT - WEBSITE_PAGINATION_LIMIT, - WEBSITE_PAGINATION_LIMIT, - $response->total - ); - - foreach ($results as $result) - { - if ($magnet = $db->getMagnet($result->magnetid)) - { - // Get access info - $accessRead = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved)); - $accessEdit = ($user->address == $db->getUser($magnet->userId)->address || in_array($user->address, MODERATOR_IP_LIST)); - - // Keywords - $keywords = []; - - foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $keyword) - { - $keywords[] = $db->getKeywordTopic($keyword->keywordTopicId)->value; - } - - $response->magnets[] = (object) - [ - 'magnetId' => $magnet->magnetId, - 'title' => $magnet->title ? htmlentities($magnet->title) : ($magnet->dn ? htmlentities($magnet->dn): false), - 'preview' => $magnet->preview ? nl2br( - htmlentities( - $magnet->preview - ) - ) : false, - 'approved' => (bool) $magnet->approved, - 'public' => (bool) $magnet->public, - 'sensitive' => (bool) $magnet->sensitive, - 'comments' => (bool) $magnet->comments, - 'timeAdded' => $magnet->timeAdded ? Time::ago((int) $magnet->timeAdded) : false, - 'timeUpdated' => $magnet->timeUpdated ? Time::ago((int) $magnet->timeUpdated) : false, - 'keywords' => $keywords, - 'comment' => (object) - [ - 'total' => $db->findMagnetCommentsTotalByMagnetId($magnet->magnetId), - 'status' => $db->findMagnetCommentsTotal($magnet->magnetId, $userId), - ], - 'download' => (object) - [ - 'total' => $db->findMagnetDownloadsTotalByMagnetId($magnet->magnetId), - 'status' => $db->findMagnetDownloadsTotal($magnet->magnetId, $userId), - ], - 'star' => (object) - [ - 'total' => $db->findMagnetStarsTotalByMagnetId($magnet->magnetId, true), - 'status' => $db->findLastMagnetStarValue($magnet->magnetId, $userId), - ], - 'access' => (object) - [ - 'read' => $accessRead, - 'edit' => $accessEdit, - ], - 'seeders' => $db->getMagnetToAddressTrackerSeedersSumByMagnetId($magnet->magnetId), - 'completed' => $db->getMagnetToAddressTrackerCompletedSumByMagnetId($magnet->magnetId), - 'leechers' => $db->getMagnetToAddressTrackerLeechersSumByMagnetId($magnet->magnetId), - 'directs' => $db->getMagnetToAcceptableSourceTotalByMagnetId($magnet->magnetId) - ]; - } - } -} - -if (isset($_GET['rss']) && $response->success) { ?>' . PHP_EOL ?> - - - - <?php echo !empty($request->query) ? sprintf(_('%s - Search - %s'), htmlspecialchars($request->query, ENT_QUOTES, 'UTF-8'), WEBSITE_NAME) - : WEBSITE_NAME ?> - - query))) ?> - magnets as $magnet) { ?> - access->read) { ?> - - <?php echo htmlspecialchars($magnet->title, ENT_QUOTES, 'UTF-8') ?> - preview), ENT_QUOTES, 'UTF-8') ?> - magnetId) ?> - magnetId) ?> - - - - - - - - - - - - - <?php echo sprintf(_('%s - Search - %s'), - htmlspecialchars($request->query, ENT_QUOTES, 'UTF-8'), - WEBSITE_NAME) ?> - - - - - - - -
-
- -
-
-
-
-
-
- success) { ?> - magnets) { ?> - magnets as $magnet) { ?> - access->read) { ?> - -
-
- -

title ?>

- leechers && !$magnet->seeders) { ?> - - - - -
-
- public) { ?> - - - - - - - - approved) { ?> - - - - - - - access->edit) { ?> - - - - - - - -
- preview) { ?> -
preview ?>
- - keywords) { ?> -
- keywords as $keyword) { ?> - - # - - -
- -
- - - - timeUpdated ? _('Updated') : _('Added') ?> - timeUpdated ? $magnet->timeUpdated : $magnet->timeAdded ?> - - - - - - - seeders ?> - - - - - - completed ?> - - - - - - - leechers ?> - - directs) { ?> - - - - - directs ?> - - - - - - star->status) { ?> - - - - - - - - - - star->total ?> - - comments) { ?> - - - comment->status) { ?> - - - - - - - - - - comment->total ?> - - - - - download->status) { ?> - - - - - - - - - - download->total ?> - -
-
- - - - - -
-

- -

-
-
- - -
-
message ?>
-
- -
-
- total > WEBSITE_PAGINATION_LIMIT) { ?> -
-
- page, ceil($response->total / WEBSITE_PAGINATION_LIMIT)) ?> - page > 1) { ?> - - - - - page < ceil($response->total / WEBSITE_PAGINATION_LIMIT)) { ?> - - - - -
-
- -
-
-
-
-
-
- $tracker) { ?> - announce) && !empty($tracker->stats)) { ?> - - / - - | - - - - | - - | - - - - - | - - - | - -
-
-
-
- - - \ No newline at end of file From 6b112d441ce9afff8f53afb2093087ec3cc7c92a Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 18:56:21 +0300 Subject: [PATCH 022/434] draft images feature #14 --- src/app/controller/submit.php | 13 +++++++++++- src/app/model/validator.php | 11 ++++++++++ src/app/view/theme/default/submit.phtml | 27 ++++++++++++++++++++++++- src/config/validator.json | 15 ++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/app/controller/submit.php b/src/app/controller/submit.php index 844e49c..834d407 100644 --- a/src/app/controller/submit.php +++ b/src/app/controller/submit.php @@ -106,12 +106,23 @@ class AppControllerSubmit ), ] ], + 'image' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'required' => $this->_validator->getPageImageRequired(), + 'accept' => implode(',', $this->_validator->getPageImageMimeTypes()), + 'placeholder' => sprintf( + _('Image file (use Ctrl to select multiple files)') + ), + ], + ], 'torrent' => (object) [ 'error' => [], 'attribute' => (object) [ - 'value' => null, 'required' => $this->_validator->getPageTorrentRequired(), 'accept' => implode(',', $this->_validator->getPageTorrentMimeTypes()), 'placeholder' => sprintf( diff --git a/src/app/model/validator.php b/src/app/model/validator.php index 5f98697..fdf63b9 100644 --- a/src/app/model/validator.php +++ b/src/app/model/validator.php @@ -298,6 +298,17 @@ class AppModelValidator return true; } + /// Page image + public function getPageImageRequired() : bool + { + return $this->_config->page->image->required; + } + + public function getPageImageMimeTypes() : array + { + return $this->_config->page->image->mime; + } + /// Page torrent public function getPageTorrentRequired() : bool { diff --git a/src/app/view/theme/default/submit.phtml b/src/app/view/theme/default/submit.phtml index ed70c89..cec2769 100644 --- a/src/app/view/theme/default/submit.phtml +++ b/src/app/view/theme/default/submit.phtml @@ -89,9 +89,34 @@ minlength="keywords->attribute->minlength ?>" maxlength="keywords->attribute->maxlength ?>">keywords->attribute->value ?> +
+ + + + + + + image->error as $errors) { ?> + +
+ +
+ + + image->attribute->required ? 'required="required"' : false ?> /> +
diff --git a/src/config/validator.json b/src/config/validator.json index 4cbe476..aaab291 100644 --- a/src/config/validator.json +++ b/src/config/validator.json @@ -39,6 +39,21 @@ "max": 20 } }, + "image": + { + "required": false, + "mime": [ + "image/png", + "image/gif", + "image/jpeg", + "image/webp" + ], + "quantity": + { + "min": 0, + "max": 20 + } + }, "torrent": { "required": true, From e1054cd69e7f299bb6c0c72310cafd937e9ca280 Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 23:05:50 +0300 Subject: [PATCH 023/434] add Environment library --- src/library/environment.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/library/environment.php diff --git a/src/library/environment.php b/src/library/environment.php new file mode 100644 index 0000000..8d85b41 --- /dev/null +++ b/src/library/environment.php @@ -0,0 +1,27 @@ + Date: Sun, 24 Sep 2023 23:18:51 +0300 Subject: [PATCH 024/434] rewrite config files to JSON, refactor environment bootstrap #14 --- .gitignore | 5 + README.md | 5 +- example/environment/env.example.php | 216 ---------------------------- src/app/controller/index.php | 4 +- src/app/controller/response.php | 4 +- src/app/controller/welcome.php | 4 +- src/app/model/database.php | 49 +++++-- src/config/bootstrap.php | 116 +++++++-------- src/config/database.json | 7 + src/config/memcached.json | 6 + src/config/moderators.json | 3 + src/config/sphinx.json | 4 + src/config/website.json | 4 + 13 files changed, 127 insertions(+), 300 deletions(-) delete mode 100644 example/environment/env.example.php create mode 100644 src/config/database.json create mode 100644 src/config/memcached.json create mode 100644 src/config/moderators.json create mode 100644 src/config/sphinx.json create mode 100644 src/config/website.json diff --git a/.gitignore b/.gitignore index afb0638..9dca305 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,12 @@ /src/config/* !/src/config/bootstrap.php +!/src/config/website.json +!/src/config/sphinx.json +!/src/config/memcached.json +!/src/config/database.json !/src/config/validator.json +!/src/config/moderators.json !/src/config/nodes.json !/src/config/trackers.json !/src/config/peers.json diff --git a/README.md b/README.md index c1dcba8..6cb88b8 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,9 @@ memcached * The web root dir is `/src/public` * Deploy the database using [MySQL Workbench](https://www.mysql.com/products/workbench) project presented in the `/database` folder * Install [Sphinx Search Server](https://sphinxsearch.com) -* Configuration examples presented at `/example/environment` folder. On first app launch, configuration file will be auto-generated in `/src/config` -* Make sure `/src/api` folder writable +* Server environment examples presented at `/example/environment` folder +* App config available at `/src/config` folder in JSON format. + + To make environment-based configuration for JSON files, create subfolder `/src/config/env` and define `env` in `/src/config/.env` file #### Contribute diff --git a/example/environment/env.example.php b/example/environment/env.example.php deleted file mode 100644 index 2e839d7..0000000 --- a/example/environment/env.example.php +++ /dev/null @@ -1,216 +0,0 @@ - 'text/css', 'href' => sprintf( 'assets/theme/default/css/common.css?%s', - WEBSITE_CSS_VERSION + CSS_VERSION ), ], [ @@ -93,7 +93,7 @@ class AppControllerIndex 'type' => 'text/css', 'href' => sprintf( 'assets/theme/default/css/framework.css?%s', - WEBSITE_CSS_VERSION + CSS_VERSION ), ], ] diff --git a/src/app/controller/response.php b/src/app/controller/response.php index 9826b13..3961e4b 100644 --- a/src/app/controller/response.php +++ b/src/app/controller/response.php @@ -38,7 +38,7 @@ class AppControllerResponse 'type' => 'text/css', 'href' => sprintf( 'assets/theme/default/css/common.css?%s', - WEBSITE_CSS_VERSION + CSS_VERSION ), ], [ @@ -46,7 +46,7 @@ class AppControllerResponse 'type' => 'text/css', 'href' => sprintf( 'assets/theme/default/css/framework.css?%s', - WEBSITE_CSS_VERSION + CSS_VERSION ), ], ] diff --git a/src/app/controller/welcome.php b/src/app/controller/welcome.php index 611be0d..0f034dd 100644 --- a/src/app/controller/welcome.php +++ b/src/app/controller/welcome.php @@ -92,7 +92,7 @@ class AppControllerWelcome 'type' => 'text/css', 'href' => sprintf( 'assets/theme/default/css/common.css?%s', - WEBSITE_CSS_VERSION + CSS_VERSION ), ], [ @@ -100,7 +100,7 @@ class AppControllerWelcome 'type' => 'text/css', 'href' => sprintf( 'assets/theme/default/css/framework.css?%s', - WEBSITE_CSS_VERSION + CSS_VERSION ), ], ] diff --git a/src/app/model/database.php b/src/app/model/database.php index ec435cb..6d67d26 100644 --- a/src/app/model/database.php +++ b/src/app/model/database.php @@ -1,17 +1,36 @@ _db = new PDO( + 'mysql:dbname=' . $config->name . ';host=' . $config->host . ';port=' . $config->port . ';charset=utf8', + $config->user, + $config->password, + [ + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' + ] + ); - $this->_db = new PDO('mysql:dbname=' . $database . ';host=' . $host . ';port=' . $port . ';charset=utf8', $username, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); - $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->_db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); - $this->_db->setAttribute(PDO::ATTR_TIMEOUT, 600); + $this->_db->setAttribute( + PDO::ATTR_ERRMODE, + PDO::ERRMODE_EXCEPTION + ); + + $this->_db->setAttribute( + PDO::ATTR_DEFAULT_FETCH_MODE, + PDO::FETCH_OBJ + ); + + $this->_db->setAttribute( + PDO::ATTR_TIMEOUT, + 600 + ); $this->_debug = (object) [ @@ -38,23 +57,23 @@ class AppModelDatabase { } // Tools - public function beginTransaction() { - + public function beginTransaction() : void + { $this->_db->beginTransaction(); } - public function commit() { - + public function commit() : void + { $this->_db->commit(); } - public function rollBack() { - + public function rollBack() : void + { $this->_db->rollBack(); } - public function getDebug() { - + public function getDebug() : object + { return $this->_debug; } diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index 22f72b8..9617217 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -3,42 +3,20 @@ // PHP declare(strict_types=1); +// Debug +ini_set('display_errors', '1'); +ini_set('display_startup_errors', '1'); +error_reporting(E_ALL); + // Application define('APP_VERSION', '2.0.0'); define('API_VERSION', APP_VERSION); +define('CSS_VERSION', APP_VERSION); -// Init environment -if (!file_exists(__DIR__ . '/.env')) -{ - if ($handle = fopen(__DIR__ . '/.env', 'w+')) - { - fwrite($handle, 'default'); - fclose($handle); +// Environment +require_once __DIR__ . '/../library/environment.php'; - chmod(__DIR__ . '/.env', 0770); - } - - else exit (_('Could not init environment file. Please check permissions.')); -} - -define('PHP_ENV', file_get_contents(__DIR__ . '/.env')); - -// Init config -if (!file_exists(__DIR__ . '/env.' . PHP_ENV . '.php')) -{ - if (copy(__DIR__ . '/../../example/environment/env.example.php', - __DIR__ . '/env.' . PHP_ENV . '.php')) - { - chmod(__DIR__ . '/env.' . PHP_ENV . '.php', 0770); - } - - else exit (_('Could not init configuration file. Please check permissions.')); -} - -// Load environment -require_once __DIR__ . '/env.' . PHP_ENV . '.php'; - -// Autoload vendors +// Autoload require_once __DIR__ . '/../../vendor/autoload.php'; // Route @@ -50,93 +28,109 @@ if (isset($request['_route_'])) { case 'stars': - require_once(__DIR__ . '/../app/controller/stars.php'); + require_once __DIR__ . '/../app/controller/stars.php'; - $controller = new AppControllerStars(); + $appControllerStars = new AppControllerStars(); + + $appControllerStars->render(); break; case 'views': - require_once(__DIR__ . '/../app/controller/views.php'); + require_once __DIR__ . '/../app/controller/views.php'; - $controller = new AppControllerViews(); + $appControllerViews = new AppControllerViews(); + + $appControllerViews->render(); break; case 'downloads': - require_once(__DIR__ . '/../app/controller/downloads.php'); + require_once __DIR__ . '/../app/controller/downloads.php'; - $controller = new AppControllerDownloads(); + $appControllerDownloads = new AppControllerDownloads(); + + $appControllerDownloads->render(); break; case 'comments': - require_once(__DIR__ . '/../app/controller/comments.php'); + require_once __DIR__ . '/../app/controller/comments.php'; - $controller = new AppControllerComments(); + $appControllerComments = new AppControllerComments(); + + $appControllerComments->render(); break; case 'editions': - require_once(__DIR__ . '/../app/controller/editions.php'); + require_once __DIR__ . '/../app/controller/editions.php'; - $controller = new AppControllerEditions(); + $appControllerEditions = new AppControllerEditions(); + + $appControllerEditions->render(); break; case 'welcome': - require_once(__DIR__ . '/../app/controller/welcome.php'); + require_once __DIR__ . '/../app/controller/welcome.php'; - $controller = new AppControllerWelcome(); + $appControllerWelcome = new AppControllerWelcome(); + + $appControllerWelcome->render(); break; - case 'submit': + case 'page/form': - require_once(__DIR__ . '/../app/model/validator.php'); + require_once __DIR__ . '/../app/controller/page.php'; - $validator = new AppModelValidator( - json_decode( - file_get_contents( - __DIR__ . '/../config/validator.json' - ) - ) + $appControllerPage = new AppControllerPage( + Environment::config('website') ); - require_once(__DIR__ . '/../app/controller/submit.php'); + require_once __DIR__ . '/../app/model/database.php'; + require_once __DIR__ . '/../app/model/validator.php'; - $controller = new AppControllerSubmit( - $validator + $appControllerPage->renderFormDescription( + new AppModelDatabase( + Environment::config('database') + ), + new AppModelValidator( + Environment::config('validator') + ) ); break; default: - require_once(__DIR__ . '/../app/controller/response.php'); + require_once __DIR__ . '/../app/controller/response.php'; - $controller = new AppControllerResponse( + $appControllerResponse = new AppControllerResponse( sprintf( _('404 - Not found - %s'), - WEBSITE_NAME + Environment::config('website')->name ), _('404'), _('Page not found'), 404 ); + + $appControllerResponse->render(); } } else { - require_once(__DIR__ . '/../app/controller/index.php'); + require_once __DIR__ . '/../app/controller/index.php'; - $controller = new AppControllerIndex(); + $appControllerIndex = new AppControllerIndex(); + + $appControllerIndex->render(); } - -$controller->render(); \ No newline at end of file diff --git a/src/config/database.json b/src/config/database.json new file mode 100644 index 0000000..64d4306 --- /dev/null +++ b/src/config/database.json @@ -0,0 +1,7 @@ +{ + "port":3306, + "host":"127.0.0.1", + "name":"", + "user":"", + "password":"" +} \ No newline at end of file diff --git a/src/config/memcached.json b/src/config/memcached.json new file mode 100644 index 0000000..c6dd071 --- /dev/null +++ b/src/config/memcached.json @@ -0,0 +1,6 @@ +{ + "port": 11211, + "host": "127.0.0.1", + "namespace": "", + "timeout": 3600 +} \ No newline at end of file diff --git a/src/config/moderators.json b/src/config/moderators.json new file mode 100644 index 0000000..b344d1e --- /dev/null +++ b/src/config/moderators.json @@ -0,0 +1,3 @@ +[ + "" +] diff --git a/src/config/sphinx.json b/src/config/sphinx.json new file mode 100644 index 0000000..6115883 --- /dev/null +++ b/src/config/sphinx.json @@ -0,0 +1,4 @@ +{ + "port":9306, + "host":"127.0.0.1" +} \ No newline at end of file diff --git a/src/config/website.json b/src/config/website.json new file mode 100644 index 0000000..580a5a9 --- /dev/null +++ b/src/config/website.json @@ -0,0 +1,4 @@ +{ + "name":"YGGtracker", + "url":"" +} \ No newline at end of file From e4d1215a53d471570b94fa466cad242fb5994af6 Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 24 Sep 2023 23:49:01 +0300 Subject: [PATCH 025/434] remove deprecated controller --- src/app/controller/submit.php | 240 ---------------------------------- 1 file changed, 240 deletions(-) delete mode 100644 src/app/controller/submit.php diff --git a/src/app/controller/submit.php b/src/app/controller/submit.php deleted file mode 100644 index 834d407..0000000 --- a/src/app/controller/submit.php +++ /dev/null @@ -1,240 +0,0 @@ -_validator = $validator; - } - - private function _response(string $title, string $h1, string $text, int $code = 200) - { - require_once __DIR__ . '/response.php'; - - $appControllerResponse = new AppControllerResponse( - $title, - $h1, - $text, - $code - ); - - $appControllerResponse->render(); - - exit; - } - - public function render() - { - require_once __DIR__ . '/user.php'; - - $appControllerUser = new AppControllerUser( - $_SERVER['REMOTE_ADDR'] - ); - - // Get user info - if (!$user = $appControllerUser->getUser()) - { - $this->_response( - sprintf( - _('Error - %s'), - WEBSITE_NAME - ), - _('500'), - _('Could not init user'), - 500 - ); - } - - // Require account type selection - if (is_null($user->public)) - { - header( - sprintf('Location: %s/welcome', trim(WEBSITE_URL, '/')) - ); - } - - // Form - $form = (object) - [ - 'title' => (object) - [ - 'error' => [], - 'attribute' => (object) - [ - 'value' => null, - 'required' => $this->_validator->getPageTitleRequired(), - 'minlength' => $this->_validator->getPageTitleLengthMin(), - 'maxlength' => $this->_validator->getPageTitleLengthMax(), - 'placeholder' => sprintf( - _('Page subject (%s-%s chars)'), - number_format($this->_validator->getPageTitleLengthMin()), - number_format($this->_validator->getPageTitleLengthMax()) - ), - ] - ], - 'description' => (object) - [ - 'error' => [], - 'attribute' => (object) - [ - 'value' => null, - 'required' => $this->_validator->getPageDescriptionRequired(), - 'minlength' => $this->_validator->getPageDescriptionLengthMin(), - 'maxlength' => $this->_validator->getPageDescriptionLengthMax(), - 'placeholder' => sprintf( - _('Page description text (%s-%s chars)'), - number_format($this->_validator->getPageDescriptionLengthMin()), - number_format($this->_validator->getPageDescriptionLengthMax()) - ), - ] - ], - 'keywords' => (object) - [ - 'error' => [], - 'attribute' => (object) - [ - 'value' => null, - 'required' => $this->_validator->getPageKeywordsRequired(), - 'placeholder' => sprintf( - _('Page keywords (%s-%s total / %s-%s chars per item)'), - number_format($this->_validator->getPageKeywordsQuantityMin()), - number_format($this->_validator->getPageKeywordsQuantityMax()), - number_format($this->_validator->getPageKeywordLengthMin()), - number_format($this->_validator->getPageKeywordLengthMax()) - ), - ] - ], - 'image' => (object) - [ - 'error' => [], - 'attribute' => (object) - [ - 'required' => $this->_validator->getPageImageRequired(), - 'accept' => implode(',', $this->_validator->getPageImageMimeTypes()), - 'placeholder' => sprintf( - _('Image file (use Ctrl to select multiple files)') - ), - ], - ], - 'torrent' => (object) - [ - 'error' => [], - 'attribute' => (object) - [ - 'required' => $this->_validator->getPageTorrentRequired(), - 'accept' => implode(',', $this->_validator->getPageTorrentMimeTypes()), - 'placeholder' => sprintf( - _('Torrent file (use Ctrl to select multiple files)') - ), - ], - ], - 'magnet' => (object) - [ - 'error' => [], - 'value' => null, - ], - ]; - - // Submit request - if (isset($_POST)) - { - if (isset($_POST['title'])) - { - $error = []; - - if (!$this->_validator->pageTitle($_POST['title'], $error)) - { - $form->title->error[] = $error; - } - - // @TODO check for page duplicates - - $form->title->attribute->value = htmlentities($_POST['title']); - } - - if (isset($_POST['description'])) - { - $error = []; - - if (!$this->_validator->pageDescription($_POST['description'], $error)) - { - $form->description->error[] = $error; - } - - $form->description->attribute->value = htmlentities($_POST['description']); - } - - if (isset($_POST['keywords'])) - { - $error = []; - - if (!$this->_validator->pageKeywords($_POST['keywords'], $error)) - { - $form->keywords->error[] = $error; - } - - $form->keywords->attribute->value = htmlentities($_POST['keywords']); - } - - if (isset($_FILES['torrent'])) - { - $error = []; - - // @TODO - } - - // Request valid - if (empty($error)) - { - // @TODO redirect - } - } - - // Render - require_once __DIR__ . '/module/head.php'; - - $appControllerModuleHead = new AppControllerModuleHead( - WEBSITE_URL, - sprintf( - _('Submit - %s'), - WEBSITE_NAME - ), - [ - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/common.css?%s', - WEBSITE_CSS_VERSION - ), - ], - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/framework.css?%s', - WEBSITE_CSS_VERSION - ), - ], - ] - ); - - require_once __DIR__ . '/module/profile.php'; - - $appControllerModuleProfile = new AppControllerModuleProfile( - $appControllerUser - ); - - require_once __DIR__ . '/module/header.php'; - - $appControllerModuleHeader = new AppControllerModuleHeader(); - - require_once __DIR__ . '/module/footer.php'; - - $appControllerModuleFooter = new AppControllerModuleFooter(); - - include __DIR__ . '../../view/theme/default/submit.phtml'; - } -} \ No newline at end of file From 556a135b594e7b3827e31dfea60e78ae9d4cf98a Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 00:31:49 +0300 Subject: [PATCH 026/434] add host rules --- src/config/validator.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/validator.json b/src/config/validator.json index aaab291..73e2931 100644 --- a/src/config/validator.json +++ b/src/config/validator.json @@ -1,4 +1,8 @@ { + "host": + { + "regex": "/^0{0,1}[2-3][a-f0-9]{0,2}:/" + }, "page": { "title": From 3a858648dea56fc78727bcd60f5b281898b016b2 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 00:32:04 +0300 Subject: [PATCH 027/434] add settings --- src/config/website.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/config/website.json b/src/config/website.json index 580a5a9..ab30b6e 100644 --- a/src/config/website.json +++ b/src/config/website.json @@ -1,4 +1,15 @@ { "name":"YGGtracker", - "url":"" + "url":"", + "default": + { + "user": + { + "approved": false + } + }, + "api": + { + "export": true + } } \ No newline at end of file From 9aaa5d5989253b97af0b2817b045f6649db258ea Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 00:32:41 +0300 Subject: [PATCH 028/434] update config dependencies to json --- src/app/model/validator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/model/validator.php b/src/app/model/validator.php index fdf63b9..2e35ada 100644 --- a/src/app/model/validator.php +++ b/src/app/model/validator.php @@ -346,14 +346,14 @@ class AppModelValidator return false; } - if (!preg_match(YGGDRASIL_HOST_REGEX, str_replace(['[',']'], false, $value))) + if (!preg_match($this->_config->host->regex, str_replace(['[',']'], false, $value))) { array_push( $error, sprintf( _('Host "%s" not match condition "%s"'), $value, - YGGDRASIL_HOST_REGEX + $this->_config->host->regex ) ); From d94947473749d877f47c24b26d73ed2d4df49b60 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 00:35:16 +0300 Subject: [PATCH 029/434] force object return --- src/library/environment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/environment.php b/src/library/environment.php index 8d85b41..b85516d 100644 --- a/src/library/environment.php +++ b/src/library/environment.php @@ -18,7 +18,7 @@ class Environment } } - return json_decode( + return (object) json_decode( file_get_contents( $config ) From 5a3ac70fd29b31ac98e2b44c33b120a47e991c39 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 01:01:48 +0300 Subject: [PATCH 030/434] update bootstrap --- src/app/controller/module/footer.php | 16 +- src/app/controller/module/header.php | 2 +- src/app/controller/page.php | 209 ++++++++++++++++++ src/app/controller/user.php | 54 +++-- .../view/theme/default/module/footer.phtml | 2 +- .../view/theme/default/module/profile.phtml | 2 +- .../theme/default/page/form/description.phtml | 115 ++++++++++ src/config/bootstrap.php | 16 +- 8 files changed, 356 insertions(+), 60 deletions(-) create mode 100644 src/app/controller/page.php create mode 100644 src/app/view/theme/default/page/form/description.phtml diff --git a/src/app/controller/module/footer.php b/src/app/controller/module/footer.php index d1a1f81..a512e0e 100644 --- a/src/app/controller/module/footer.php +++ b/src/app/controller/module/footer.php @@ -4,21 +4,9 @@ class AppControllerModuleFooter { public function render() { - $response['trackers'] = []; + $trackers = Environment::config('trackers'); - if ($trackers = json_decode(file_get_contents(__DIR__ . '/../../../config/trackers.json'))) - { - foreach ($trackers as $tracker) - { - if (!empty($tracker->announce) && !empty($tracker->stats)) - { - $response['trackers'][] = [ - 'announce' => $tracker->announce, - 'stats' => $tracker->stats, - ]; - } - } - } + $api = Environment::config('website')->api->export; include __DIR__ . '../../../view/theme/default/module/footer.phtml'; } diff --git a/src/app/controller/module/header.php b/src/app/controller/module/header.php index df162b1..293a268 100644 --- a/src/app/controller/module/header.php +++ b/src/app/controller/module/header.php @@ -7,7 +7,7 @@ class AppControllerModuleHeader $name = str_replace( 'YGG', 'YGG', - WEBSITE_NAME + Environment::config('website')->name ); require_once __DIR__ . '/search.php'; diff --git a/src/app/controller/page.php b/src/app/controller/page.php new file mode 100644 index 0000000..0d0d873 --- /dev/null +++ b/src/app/controller/page.php @@ -0,0 +1,209 @@ +_database = new AppModelDatabase( + Environment::config('database') + ); + + require_once __DIR__ . '/../model/validator.php'; + + $this->_validator = new AppModelValidator( + Environment::config('validator') + ); + + require_once __DIR__ . '/user.php'; + + $this->_user = new AppControllerUser( + $_SERVER['REMOTE_ADDR'] + ); + } + + private function _response(string $title, string $h1, string $text, int $code = 200) + { + require_once __DIR__ . '/response.php'; + + $appControllerResponse = new AppControllerResponse( + $title, + $h1, + $text, + $code + ); + + $appControllerResponse->render(); + + exit; + } + + public function renderFormDescription() + { + // Init form + $form = (object) + [ + 'title' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'required' => $this->_validator->getPageTitleRequired(), + 'minlength' => $this->_validator->getPageTitleLengthMin(), + 'maxlength' => $this->_validator->getPageTitleLengthMax(), + 'placeholder' => sprintf( + _('Page subject (%s-%s chars)'), + number_format($this->_validator->getPageTitleLengthMin()), + number_format($this->_validator->getPageTitleLengthMax()) + ), + ] + ], + 'description' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'required' => $this->_validator->getPageDescriptionRequired(), + 'minlength' => $this->_validator->getPageDescriptionLengthMin(), + 'maxlength' => $this->_validator->getPageDescriptionLengthMax(), + 'placeholder' => sprintf( + _('Page description text (%s-%s chars)'), + number_format($this->_validator->getPageDescriptionLengthMin()), + number_format($this->_validator->getPageDescriptionLengthMax()) + ), + ] + ], + 'keywords' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'required' => $this->_validator->getPageKeywordsRequired(), + 'placeholder' => sprintf( + _('Page keywords (%s-%s total / %s-%s chars per item)'), + number_format($this->_validator->getPageKeywordsQuantityMin()), + number_format($this->_validator->getPageKeywordsQuantityMax()), + number_format($this->_validator->getPageKeywordLengthMin()), + number_format($this->_validator->getPageKeywordLengthMax()) + ), + ] + ], + 'sensitive' => (object) + [ + 'error' => [], + 'attribute' => (object) + [ + 'value' => null, + 'placeholder' => _('Apply NSFW filters for this publication'), + ] + ] + ]; + + // Submit request + if (isset($_POST)) + { + if (isset($_POST['title'])) + { + $error = []; + + if (!$this->_validator->pageTitle($_POST['title'], $error)) + { + $form->title->error[] = $error; + } + + // @TODO check for page duplicates + + $form->title->attribute->value = htmlentities($_POST['title']); + } + + if (isset($_POST['description'])) + { + $error = []; + + if (!$this->_validator->pageDescription($_POST['description'], $error)) + { + $form->description->error[] = $error; + } + + $form->description->attribute->value = htmlentities($_POST['description']); + } + + if (isset($_POST['keywords'])) + { + $error = []; + + if (!$this->_validator->pageKeywords($_POST['keywords'], $error)) + { + $form->keywords->error[] = $error; + } + + $form->keywords->attribute->value = htmlentities($_POST['keywords']); + } + + if (isset($_POST['sensitive'])) + { + $form->sensitive->attribute->value = (bool) $_POST['sensitive']; + } + + // Request valid + if (empty($error)) + { + // @TODO redirect + } + } + + // Render template + require_once __DIR__ . '/module/head.php'; + + $appControllerModuleHead = new AppControllerModuleHead( + Environment::config('website')->url, + sprintf( + _('Submit - %s'), + Environment::config('website')->name + ), + [ + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/common.css?%s', + CSS_VERSION + ), + ], + [ + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => sprintf( + 'assets/theme/default/css/framework.css?%s', + CSS_VERSION + ), + ], + ] + ); + + require_once __DIR__ . '/module/profile.php'; + + $appControllerModuleProfile = new AppControllerModuleProfile( + $this->_user + ); + + require_once __DIR__ . '/module/header.php'; + + $appControllerModuleHeader = new AppControllerModuleHeader(); + + require_once __DIR__ . '/module/footer.php'; + + $appControllerModuleFooter = new AppControllerModuleFooter(); + + include __DIR__ . '../../view/theme/default/page/form/description.phtml'; + } +} \ No newline at end of file diff --git a/src/app/controller/user.php b/src/app/controller/user.php index 0f6b883..85a83a2 100644 --- a/src/app/controller/user.php +++ b/src/app/controller/user.php @@ -3,48 +3,33 @@ class AppControllerUser { private $_database; + private $_validator; private $_user; public function __construct(string $address) { - // Connect DB require_once __DIR__ . '/../model/database.php'; - try - { - $this->_database = new AppModelDatabase( - DB_HOST, - DB_PORT, - DB_NAME, - DB_USERNAME, - DB_PASSWORD - ); - } + $this->_database = new AppModelDatabase( + Environment::config('database') + ); - catch (Exception $error) - { - $this->_response( - sprintf( - _('Error - %s'), - WEBSITE_NAME - ), - _('500'), - print_r($error, true), - 500 - ); - } + require_once __DIR__ . '/../model/validator.php'; - // Validate user address - require_once __DIR__ . '/../../library/valid.php'; + $this->_validator = new AppModelValidator( + Environment::config('validator') + ); + // Validate address $error = []; - if (!Valid::host($address, $error)) + + if (!$this->_validator->host($address, $error)) { $this->_response( sprintf( _('Error - %s'), - WEBSITE_NAME + Environment::config('website')->name ), _('406'), print_r($error, true), @@ -60,7 +45,7 @@ class AppControllerUser $this->_user = $this->_database->getUser( $this->_database->initUserId( $address, - USER_DEFAULT_APPROVED, + Environment::config('website')->default->user->approved, time() ) ); @@ -75,13 +60,24 @@ class AppControllerUser $this->_response( sprintf( _('Error - %s'), - WEBSITE_NAME + Environment::config('website')->name ), _('500'), print_r($error, true), 500 ); } + + // Require account type selection + if (is_null($this->getPublic())) + { + header( + sprintf( + 'Location: %s/welcome', + trim($this->_config->url, '/') + ) + ); + } } private function _response(string $title, string $h1, string $text, int $code = 200) diff --git a/src/app/view/theme/default/module/footer.phtml b/src/app/view/theme/default/module/footer.phtml index 75aeaf3..0064221 100644 --- a/src/app/view/theme/default/module/footer.phtml +++ b/src/app/view/theme/default/module/footer.phtml @@ -13,7 +13,7 @@ | - + | diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml index 6b774fc..a3062dc 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/src/app/view/theme/default/module/profile.phtml @@ -152,7 +152,7 @@ - + diff --git a/src/app/view/theme/default/page/form/description.phtml b/src/app/view/theme/default/page/form/description.phtml new file mode 100644 index 0000000..33df21b --- /dev/null +++ b/src/app/view/theme/default/page/form/description.phtml @@ -0,0 +1,115 @@ + + + render() ?> + + render() ?> +
+
+
+
+ render() ?> +
+
+

+
+
+
+ + + + + + + title->error as $errors) { ?> + +
+ +
+ + + title->attribute->required ? 'required="required"' : false ?> + value="title->attribute->value ?>" + placeholder="title->attribute->placeholder ?>" + minlength="title->attribute->minlength ?>" + maxlength="title->attribute->maxlength ?>" /> +
+
+ + + + + + + description->error as $errors) { ?> + +
+ +
+ + + +
+
+ + + + + + + keywords->error as $errors) { ?> + +
+ +
+ + + +
+
+ sensitive->attribute->value ? 'checked="checked"' : false ?> /> + + + + + + +
+
+ +
+
+
+
+
+
+
+ render() ?> + + \ No newline at end of file diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index 9617217..23f0708 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -90,21 +90,9 @@ if (isset($request['_route_'])) require_once __DIR__ . '/../app/controller/page.php'; - $appControllerPage = new AppControllerPage( - Environment::config('website') - ); + $appControllerPage = new AppControllerPage(); - require_once __DIR__ . '/../app/model/database.php'; - require_once __DIR__ . '/../app/model/validator.php'; - - $appControllerPage->renderFormDescription( - new AppModelDatabase( - Environment::config('database') - ), - new AppModelValidator( - Environment::config('validator') - ) - ); + $appControllerPage->renderFormDescription(); break; From d77ad74d32ac0f0b01ec183d3dbb2002c5ae8a84 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 01:08:42 +0300 Subject: [PATCH 031/434] add locales registry #14 --- .gitignore | 1 + src/config/locales.json | 277 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 src/config/locales.json diff --git a/.gitignore b/.gitignore index 9dca305..1a54812 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /src/config/* !/src/config/bootstrap.php !/src/config/website.json +!/src/config/locales.json !/src/config/sphinx.json !/src/config/memcached.json !/src/config/database.json diff --git a/src/config/locales.json b/src/config/locales.json new file mode 100644 index 0000000..2c49e62 --- /dev/null +++ b/src/config/locales.json @@ -0,0 +1,277 @@ +{ + "af_ZA": + [ + "Afrikaans", + "Afrikaans" + ], + "ar": + [ + "العربية", + "Arabic" + ], + "bg_BG": + [ + "Български", + "Bulgarian" + ], + "ca_AD": + [ + "Català", + "Catalan" + ], + "cs_CZ": + [ + "Čeština", + "Czech" + ], + "cy_GB": + [ + "Cymraeg", + "Welsh" + ], + "da_DK": + [ + "Dansk", + "Danish" + ], + "de_AT": + [ + "Deutsch (Österreich)", + "German (Austria)" + ], + "de_CH": + [ + "Deutsch (Schweiz)", + "German (Switzerland)" + ], + "de_DE": + [ + "Deutsch (Deutschland)", + "German (Germany)" + ], + "el_GR": + [ + "Ελληνικά", + "Greek" + ], + "en_GB": + [ + "English (UK)", + "English (UK)" + ], + "en_US": + [ + "English (US)", + "English (US)" + ], + "es_CL": + [ + "Español (Chile)", + "Spanish (Chile)" + ], + "es_ES": + [ + "Español (España)", + "Spanish (Spain)" + ], + "es_MX": + [ + "Español (México)", + "Spanish (Mexico)" + ], + "et_EE": + [ + "Eesti keel", + "Estonian" + ], + "eu": + [ + "Euskara", + "Basque" + ], + "fa_IR": + [ + "فارسی", + "Persian" + ], + "fi_FI": + [ + "Suomi", + "Finnish" + ], + "fr_CA": + [ + "Français (Canada)", + "French (Canada)" + ], + "fr_FR": + [ + "Français (France)", + "French (France)" + ], + "gl_ES": + [ + "Galego (Spain)", + "Galician (Spain)" + ], + "he_IL": + [ + "עברית", + "Hebrew" + ], + "hi_IN": + [ + "हिंदी", + "Hindi" + ], + "hr_HR": + [ + "Hrvatski", + "Croatian" + ], + "hu_HU": + [ + "Magyar", + "Hungarian" + ], + "id_ID": + [ + "Bahasa Indonesia", + "Indonesian" + ], + "is_IS": + [ + "Íslenska", + "Icelandic" + ], + "it_IT": + [ + "Italiano", + "Italian" + ], + "ja_JP": + [ + "日本語", + "Japanese" + ], + "km_KH": + [ + "ភាសាខ្មែរ", + "Khmer" + ], + "ko_KR": + [ + "한국어", + "Korean" + ], + "la": + [ + "Latina", + "Latin" + ], + "lt_LT": + [ + "Lietuvių kalba", + "Lithuanian" + ], + "lv_LV": + [ + "Latviešu", + "Latvian" + ], + "mn_MN": + [ + "Монгол", + "Mongolian" + ], + "nb_NO": + [ + "Norsk bokmål", + "Norwegian (Bokmål)" + ], + "nl_NL": + [ + "Nederlands", + "Dutch" + ], + "nn_NO": + [ + "Norsk nynorsk", + "Norwegian (Nynorsk)" + ], + "pl_PL": + [ + "Polski", + "Polish" + ], + "pt_BR": + [ + "Português (Brasil)", + "Portuguese (Brazil)" + ], + "pt_PT": + [ + "Português (Portugal)", + "Portuguese (Portugal)" + ], + "ro_RO": + [ + "Română", + "Romanian" + ], + "ru_RU": + [ + "Русский", + "Russian" + ], + "sk_SK": + [ + "Slovenčina", + "Slovak" + ], + "sl_SI": + [ + "Slovenščina", + "Slovenian" + ], + "sr_RS": + [ + "Српски / Srpski", + "Serbian" + ], + "sv_SE": + [ + "Svenska", + "Swedish" + ], + "th_TH": + [ + "ไทย", + "Thai" + ], + "tr_TR": + [ + "Türkçe", + "Turkish" + ], + "uk_UA": + [ + "Українська", + "Ukrainian" + ], + "vi_VN": + [ + "Tiếng Việt", + "Vietnamese" + ], + "zh_CN": + [ + "中文 (中国大陆)", + "Chinese (PRC)" + ], + "zh_TW": + [ + "中文 (台灣)", + "Chinese (Taiwan)" + ] +} \ No newline at end of file From 0d840a5ab53ca5a6ac0ed4fd1107b3da86e2c6d6 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 25 Sep 2023 01:30:28 +0300 Subject: [PATCH 032/434] add content language selection #14 --- src/app/controller/page.php | 23 ++++ .../theme/default/page/form/description.phtml | 24 ++++ src/config/locales.json | 104 +++++++++--------- .../assets/theme/default/css/common.css | 1 + 4 files changed, 100 insertions(+), 52 deletions(-) diff --git a/src/app/controller/page.php b/src/app/controller/page.php index 0d0d873..b891592 100644 --- a/src/app/controller/page.php +++ b/src/app/controller/page.php @@ -46,9 +46,32 @@ class AppControllerPage public function renderFormDescription() { + // Prepare locales + $locales = []; + + foreach (Environment::config('locales') as $key => $value) + { + $locales[$key] = (object) + [ + 'key' => $key, + 'value' => $value[0], + 'active' => false !== stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], $key) ? true : false, + ]; + } + // Init form $form = (object) [ + 'locale' => (object) + [ + 'error' => [], + 'values' => $locales, + 'attribute' => (object) + [ + 'value' => null, + 'placeholder' => _('Page content language'), + ] + ], 'title' => (object) [ 'error' => [], diff --git a/src/app/view/theme/default/page/form/description.phtml b/src/app/view/theme/default/page/form/description.phtml index 33df21b..483f6da 100644 --- a/src/app/view/theme/default/page/form/description.phtml +++ b/src/app/view/theme/default/page/form/description.phtml @@ -13,6 +13,30 @@

+
+ + + + + + + +

-

address ?>

+

-

+

address ?>

+
- +
+ title="locale->placeholder ?>"> - - - -
-
- -
- - - - - - - render() ?> - - \ No newline at end of file diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php index 7b41fd6..c2e56e6 100644 --- a/src/config/bootstrap.php +++ b/src/config/bootstrap.php @@ -76,34 +76,6 @@ if (isset($request['_route_'])) break; - case 'welcome': - - require_once __DIR__ . '/../app/model/database.php'; - require_once __DIR__ . '/../app/model/validator.php'; - require_once __DIR__ . '/../app/model/website.php'; - require_once __DIR__ . '/../app/model/session.php'; - - require_once __DIR__ . '/../app/controller/welcome.php'; - - $appControllerWelcome = new AppControllerWelcome( - new AppModelDatabase( - Environment::config('database') - ), - new AppModelValidator( - Environment::config('validator') - ), - new AppModelWebsite( - Environment::config('website') - ), - new AppModelSession( - $_SERVER['REMOTE_ADDR'] - ) - ); - - $appControllerWelcome->render(); - - break; - case 'submit': require_once __DIR__ . '/../app/model/database.php'; From f51ad8d5de7a28be8286760d423949be826e525a Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 27 Sep 2023 14:20:51 +0300 Subject: [PATCH 071/434] add theme settings #17 --- .gitignore | 1 + src/config/themes.json | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 src/config/themes.json diff --git a/.gitignore b/.gitignore index 1a54812..febfe8f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ !/src/config/bootstrap.php !/src/config/website.json !/src/config/locales.json +!/src/config/themes.json !/src/config/sphinx.json !/src/config/memcached.json !/src/config/database.json diff --git a/src/config/themes.json b/src/config/themes.json new file mode 100644 index 0000000..a248bb9 --- /dev/null +++ b/src/config/themes.json @@ -0,0 +1,3 @@ +[ + "default" +] From 2eb0a30ed2e314c9300997c4caad62a5ff18b2c8 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 27 Sep 2023 15:40:49 +0300 Subject: [PATCH 072/434] update profile module --- src/app/controller/module/profile.php | 15 +- .../view/theme/default/module/profile.phtml | 365 ++++++++++-------- .../assets/theme/default/css/framework.css | 5 + 3 files changed, 212 insertions(+), 173 deletions(-) diff --git a/src/app/controller/module/profile.php b/src/app/controller/module/profile.php index f6852aa..0d82acc 100644 --- a/src/app/controller/module/profile.php +++ b/src/app/controller/module/profile.php @@ -35,11 +35,20 @@ class AppControllerModuleProfile $comments = $this->_database->findUserPageCommentsDistinctTotal($user->userId); $editions = 0; // @TODO $this->_database->findUserPageEditionsDistinctTotal($user->userId); - $identicon = false; // @TODO $this->_database->getIdenticon(24); - - $public = $user->public; $address = $user->address; + $icon = new Jdenticon\Identicon(); + + $icon->setValue($user->address); + $icon->setSize(16); + $icon->setStyle( + [ + 'backgroundColor' => 'rgba(255, 255, 255, 0)', + ] + ); + + $identicon = $icon->getImageDataUri('webp'); + include __DIR__ . '../../../view/theme/default/module/profile.phtml'; } } \ No newline at end of file diff --git a/src/app/view/theme/default/module/profile.phtml b/src/app/view/theme/default/module/profile.phtml index 6b774fc..5d54747 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/src/app/view/theme/default/module/profile.phtml @@ -1,174 +1,199 @@ -
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/public/assets/theme/default/css/framework.css b/src/public/assets/theme/default/css/framework.css index 2d54030..ea13706 100644 --- a/src/public/assets/theme/default/css/framework.css +++ b/src/public/assets/theme/default/css/framework.css @@ -272,6 +272,11 @@ a:visited.background-color-hover-night-light:hover { padding-bottom: 8px; } +.padding-y-12 { + padding-top: 12px; + padding-bottom: 12px; +} + .padding-b-16 { padding-bottom: 16px; } From e6293f90b2b7c8aff3d7e939fc25e98c85e197be Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 27 Sep 2023 15:41:05 +0300 Subject: [PATCH 073/434] add user settings fields --- database/yggtracker.mwb | Bin 43421 -> 44565 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/database/yggtracker.mwb b/database/yggtracker.mwb index db3e6bb6e12aa88148748153c66bcafc87a4c397..cb92f1e26b29395c2deb7740a00bddfb348ca7dd 100644 GIT binary patch literal 44565 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 Y?O^L-=_T)|24>0Ra=}vH$=8 literal 43421 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 Date: Fri, 29 Sep 2023 14:42:11 +0300 Subject: [PATCH 074/434] update error message --- src/app/model/validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/model/validator.php b/src/app/model/validator.php index 2e35ada..9040953 100644 --- a/src/app/model/validator.php +++ b/src/app/model/validator.php @@ -338,7 +338,7 @@ class AppModelValidator array_push( $error, sprintf( - _('Host of "%s" not supported'), + _('Host "%s" not supported'), $value ) ); From 3a4e498c3416649b03e2792ee172278031087cb6 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 29 Sep 2023 16:13:41 +0300 Subject: [PATCH 075/434] add server environment support --- src/app/model/request.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/model/request.php b/src/app/model/request.php index d63470d..ae47d3b 100644 --- a/src/app/model/request.php +++ b/src/app/model/request.php @@ -5,12 +5,14 @@ class AppModelRequest { private array $_get; private array $_post; private array $_files; + private array $_server; - public function __construct(array $get, array $post, array $files) + public function __construct(array $get, array $post, array $files, array $server) { - $this->_get = $get; - $this->_post = $post; - $this->_files = $files; + $this->_get = $get; + $this->_post = $post; + $this->_files = $files; + $this->_server = $server; } public function get(string $key) : mixed @@ -52,6 +54,19 @@ class AppModelRequest { } } + public function server(string $key) : mixed + { + if (isset($this->_get[$key])) + { + return $this->_get[$key]; + } + + else + { + return false; + } + } + public function hasPost() : bool { return !empty($this->_post); From 7f892b0772580034ddf37181a831278b6d33c11d Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 29 Sep 2023 16:15:42 +0300 Subject: [PATCH 076/434] add value definition support --- src/app/model/request.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/app/model/request.php b/src/app/model/request.php index ae47d3b..cf09450 100644 --- a/src/app/model/request.php +++ b/src/app/model/request.php @@ -15,8 +15,13 @@ class AppModelRequest { $this->_server = $server; } - public function get(string $key) : mixed + public function get(string $key, mixed $value = null) : mixed { + if ($value) + { + $this->_get[$key] = $value; + } + if (isset($this->_get[$key])) { return $this->_get[$key]; @@ -28,8 +33,13 @@ class AppModelRequest { } } - public function post(string $key) : mixed + public function post(string $key, mixed $value = null) : mixed { + if ($value) + { + $this->_get[$key] = $value; + } + if (isset($this->_post[$key])) { return $this->_post[$key]; @@ -41,8 +51,13 @@ class AppModelRequest { } } - public function files(string $key) : mixed + public function files(string $key, mixed $value = null) : mixed { + if ($value) + { + $this->_get[$key] = $value; + } + if (isset($this->_files[$key])) { return $this->_files[$key]; @@ -54,8 +69,13 @@ class AppModelRequest { } } - public function server(string $key) : mixed + public function server(string $key, mixed $value = null) : mixed { + if ($value) + { + $this->_get[$key] = $value; + } + if (isset($this->_get[$key])) { return $this->_get[$key]; From cfc9c721ff497b79ee5506091b46d92fab21d37d Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 29 Sep 2023 17:34:30 +0300 Subject: [PATCH 077/434] fix variable names --- src/app/model/request.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/model/request.php b/src/app/model/request.php index cf09450..e6697fa 100644 --- a/src/app/model/request.php +++ b/src/app/model/request.php @@ -94,11 +94,11 @@ class AppModelRequest { public function hasGet() : bool { - return !empty($this->_post); + return !empty($this->_get); } public function hasFiles() : bool { - return !empty($this->_post); + return !empty($this->_files); } } From ca50f856266a0bd804815166b848c17fff84a620 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 29 Sep 2023 20:01:34 +0300 Subject: [PATCH 078/434] separate url settings to sheme/host/port/path parts --- src/config/website.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/website.json b/src/config/website.json index 6797314..49cfcc0 100644 --- a/src/config/website.json +++ b/src/config/website.json @@ -1,6 +1,9 @@ { "name":"YGGtracker", - "url":"", + "scheme":"", + "host":"", + "port":"", + "path":"", "default": { "locale":"en-US", From 7d7629488a413201c942cb62d1ddc5c0963f7619 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 29 Sep 2023 20:02:56 +0300 Subject: [PATCH 079/434] add sef configuration example --- example/environment/nginx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/example/environment/nginx b/example/environment/nginx index 4bfd1c5..c5a243f 100644 --- a/example/environment/nginx +++ b/example/environment/nginx @@ -11,7 +11,7 @@ server { server_name _; location / { - try_files $uri $uri/ =404; + try_files $uri $uri/ =404 @yggtracker; } location ~ \.php$ { @@ -22,4 +22,8 @@ server { location ~ /\. { deny all; } + + location @yggtracker { + rewrite ^/(.*)$ /index.php?$1 last; + } } \ No newline at end of file From 3c233fcfad15087cd1f8579e64522b172985188d Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 29 Sep 2023 20:03:09 +0300 Subject: [PATCH 080/434] remove deprecated model --- src/app/model/session.php | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 src/app/model/session.php diff --git a/src/app/model/session.php b/src/app/model/session.php deleted file mode 100644 index dc9167e..0000000 --- a/src/app/model/session.php +++ /dev/null @@ -1,16 +0,0 @@ -_address = $address; - } - - public function getAddress() : string - { - return $this->_address; - } -} From 380377b27c3794dbfd37702e823dd020253377b6 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 2 Oct 2023 16:13:55 +0300 Subject: [PATCH 081/434] init symfony framework #14 --- .env | 50 + .env.test | 6 + .gitignore | 42 +- bin/console | 17 + bin/phpunit | 19 + composer.json | 112 +- composer.lock | 9748 +++++++++++++++++ config/bundles.php | 14 + config/packages/cache.yaml | 19 + config/packages/debug.yaml | 5 + config/packages/doctrine.yaml | 48 + config/packages/doctrine_migrations.yaml | 6 + config/packages/framework.yaml | 25 + config/packages/mailer.yaml | 3 + config/packages/messenger.yaml | 24 + config/packages/monolog.yaml | 61 + config/packages/notifier.yaml | 13 + config/packages/routing.yaml | 12 + config/packages/security.yaml | 39 + config/packages/translation.yaml | 15 + config/packages/twig.yaml | 9 + config/packages/validator.yaml | 13 + config/packages/web_profiler.yaml | 17 + config/preload.php | 5 + config/routes.yaml | 5 + config/routes/framework.yaml | 4 + config/routes/web_profiler.yaml | 8 + config/services.yaml | 26 + database/yggtracker.mwb | Bin 44565 -> 0 bytes docker-compose.override.yml | 14 + docker-compose.yml | 21 + example/environment/crontab | 10 - example/environment/nginx | 29 - example/environment/sphinx.conf | 71 - .../log/index.html => migrations/.gitignore | 0 phpunit.xml.dist | 38 + .../asset}/default/css/common.css | 0 .../asset}/default/css/framework.css | 74 +- public/index.php | 9 + src/Controller/.gitignore | 0 src/Controller/HomeController.php | 20 + src/Controller/PageController.php | 67 + src/Controller/ProfileController.php | 76 + src/Controller/SearchController.php | 31 + src/Entity/.gitignore | 0 src/Entity/Page.php | 27 + src/Entity/User.php | 147 + src/Kernel.php | 11 + src/Repository/.gitignore | 0 src/Repository/PageRepository.php | 48 + src/Repository/UserRepository.php | 48 + src/Service/User.php | 12 + src/app/controller/index.php | 151 - src/app/controller/module/footer.php | 13 - src/app/controller/module/head.php | 54 - src/app/controller/module/header.php | 19 - src/app/controller/module/page.php | 9 - src/app/controller/module/pagination.php | 43 - src/app/controller/module/profile.php | 54 - src/app/controller/module/search.php | 24 - src/app/controller/page.php | 428 - src/app/controller/response.php | 65 - src/app/controller/user.php | 117 - src/app/model/database.php | 1866 ---- src/app/model/locale.php | 37 - src/app/model/request.php | 104 - src/app/model/sphinx.php | 111 - src/app/model/validator.php | 2216 ---- src/app/model/website.php | 46 - src/app/view/theme/default/index.phtml | 32 - .../view/theme/default/module/footer.phtml | 27 - src/app/view/theme/default/module/head.phtml | 7 - .../view/theme/default/module/header.phtml | 10 - src/app/view/theme/default/module/page.phtml | 126 - .../theme/default/module/pagination.phtml | 15 - .../view/theme/default/module/search.phtml | 20 - .../view/theme/default/page/form/submit.phtml | 140 - src/app/view/theme/default/response.phtml | 24 - src/config/bootstrap.php | 160 - src/config/database.json | 7 - src/config/locales.json | 277 - src/config/memcached.json | 6 - src/config/moderators.json | 3 - src/config/nodes.json | 12 - src/config/peers.json | 7 - src/config/sphinx.json | 4 - src/config/themes.json | 3 - src/config/trackers.json | 23 - src/config/validator.json | 74 - src/config/website.json | 23 - src/crontab/export/feed.php | 558 - src/crontab/export/push.php | 506 - src/crontab/import/feed.php | 1193 -- src/crontab/scrape.php | 158 - src/crontab/sitemap.php | 86 - src/library/curl.php | 97 - src/library/environment.php | 27 - src/library/filter.php | 48 - src/library/scrapeer.php | 692 -- src/library/time.php | 45 - src/public/api/push.php | 938 -- src/public/index.php | 4 - symfony.lock | 279 + templates/base.html.twig | 16 + templates/default/home/index.html.twig | 2 + templates/default/layout.html.twig | 60 + templates/default/page/submit.html.twig | 129 + templates/default/profile/index.html.twig | 2 + .../default/profile/module.html.twig | 181 +- templates/default/profile/setting.html.twig | 2 + templates/default/search/index.html.twig | 10 + templates/default/search/module.html.twig | 4 + tests/bootstrap.php | 11 + translations/.gitignore | 0 114 files changed, 11525 insertions(+), 10998 deletions(-) create mode 100644 .env create mode 100644 .env.test create mode 100755 bin/console create mode 100755 bin/phpunit create mode 100644 composer.lock create mode 100644 config/bundles.php create mode 100644 config/packages/cache.yaml create mode 100644 config/packages/debug.yaml create mode 100644 config/packages/doctrine.yaml create mode 100644 config/packages/doctrine_migrations.yaml create mode 100644 config/packages/framework.yaml create mode 100644 config/packages/mailer.yaml create mode 100644 config/packages/messenger.yaml create mode 100644 config/packages/monolog.yaml create mode 100644 config/packages/notifier.yaml create mode 100644 config/packages/routing.yaml create mode 100644 config/packages/security.yaml create mode 100644 config/packages/translation.yaml create mode 100644 config/packages/twig.yaml create mode 100644 config/packages/validator.yaml create mode 100644 config/packages/web_profiler.yaml create mode 100644 config/preload.php create mode 100644 config/routes.yaml create mode 100644 config/routes/framework.yaml create mode 100644 config/routes/web_profiler.yaml create mode 100644 config/services.yaml delete mode 100644 database/yggtracker.mwb create mode 100644 docker-compose.override.yml create mode 100644 docker-compose.yml delete mode 100644 example/environment/crontab delete mode 100644 example/environment/nginx delete mode 100644 example/environment/sphinx.conf rename src/storage/log/index.html => migrations/.gitignore (100%) create mode 100644 phpunit.xml.dist rename {src/public/assets/theme => public/asset}/default/css/common.css (100%) rename {src/public/assets/theme => public/asset}/default/css/framework.css (88%) create mode 100644 public/index.php create mode 100644 src/Controller/.gitignore create mode 100644 src/Controller/HomeController.php create mode 100644 src/Controller/PageController.php create mode 100644 src/Controller/ProfileController.php create mode 100644 src/Controller/SearchController.php create mode 100644 src/Entity/.gitignore create mode 100644 src/Entity/Page.php create mode 100644 src/Entity/User.php create mode 100644 src/Kernel.php create mode 100644 src/Repository/.gitignore create mode 100644 src/Repository/PageRepository.php create mode 100644 src/Repository/UserRepository.php create mode 100644 src/Service/User.php delete mode 100644 src/app/controller/index.php delete mode 100644 src/app/controller/module/footer.php delete mode 100644 src/app/controller/module/head.php delete mode 100644 src/app/controller/module/header.php delete mode 100644 src/app/controller/module/page.php delete mode 100644 src/app/controller/module/pagination.php delete mode 100644 src/app/controller/module/profile.php delete mode 100644 src/app/controller/module/search.php delete mode 100644 src/app/controller/page.php delete mode 100644 src/app/controller/response.php delete mode 100644 src/app/controller/user.php delete mode 100644 src/app/model/database.php delete mode 100644 src/app/model/locale.php delete mode 100644 src/app/model/request.php delete mode 100644 src/app/model/sphinx.php delete mode 100644 src/app/model/validator.php delete mode 100644 src/app/model/website.php delete mode 100644 src/app/view/theme/default/index.phtml delete mode 100644 src/app/view/theme/default/module/footer.phtml delete mode 100644 src/app/view/theme/default/module/head.phtml delete mode 100644 src/app/view/theme/default/module/header.phtml delete mode 100644 src/app/view/theme/default/module/page.phtml delete mode 100644 src/app/view/theme/default/module/pagination.phtml delete mode 100644 src/app/view/theme/default/module/search.phtml delete mode 100644 src/app/view/theme/default/page/form/submit.phtml delete mode 100644 src/app/view/theme/default/response.phtml delete mode 100644 src/config/bootstrap.php delete mode 100644 src/config/database.json delete mode 100644 src/config/locales.json delete mode 100644 src/config/memcached.json delete mode 100644 src/config/moderators.json delete mode 100644 src/config/nodes.json delete mode 100644 src/config/peers.json delete mode 100644 src/config/sphinx.json delete mode 100644 src/config/themes.json delete mode 100644 src/config/trackers.json delete mode 100644 src/config/validator.json delete mode 100644 src/config/website.json delete mode 100644 src/crontab/export/feed.php delete mode 100644 src/crontab/export/push.php delete mode 100644 src/crontab/import/feed.php delete mode 100644 src/crontab/scrape.php delete mode 100644 src/crontab/sitemap.php delete mode 100644 src/library/curl.php delete mode 100644 src/library/environment.php delete mode 100644 src/library/filter.php delete mode 100644 src/library/scrapeer.php delete mode 100644 src/library/time.php delete mode 100644 src/public/api/push.php delete mode 100644 src/public/index.php create mode 100644 symfony.lock create mode 100644 templates/base.html.twig create mode 100644 templates/default/home/index.html.twig create mode 100644 templates/default/layout.html.twig create mode 100644 templates/default/page/submit.html.twig create mode 100644 templates/default/profile/index.html.twig rename src/app/view/theme/default/module/profile.phtml => templates/default/profile/module.html.twig (52%) create mode 100644 templates/default/profile/setting.html.twig create mode 100644 templates/default/search/index.html.twig create mode 100644 templates/default/search/module.html.twig create mode 100644 tests/bootstrap.php create mode 100644 translations/.gitignore diff --git a/.env b/.env new file mode 100644 index 0000000..ff72acd --- /dev/null +++ b/.env @@ -0,0 +1,50 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=EDITME +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" +# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" +###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +# Choose one of the transports below +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages +MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +###< symfony/messenger ### + +###> symfony/mailer ### +# MAILER_DSN=null://null +###< symfony/mailer ### + +###> symfony/crowdin-translation-provider ### +# CROWDIN_DSN=crowdin://PROJECT_ID:API_TOKEN@ORGANIZATION_DOMAIN.default +###< symfony/crowdin-translation-provider ### + +# YGGtracker +APP_VERSION='2.0.0' + +APP_NAME=YGGtracker \ No newline at end of file diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..9e7162f --- /dev/null +++ b/.env.test @@ -0,0 +1,6 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/.gitignore b/.gitignore index febfe8f..79ced3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,22 @@ -/.vscode/ +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ /vendor/ +###< symfony/framework-bundle ### -/database/* -!/database/yggtracker.mwb +###> phpunit/phpunit ### +/phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### -/src/public/api/*.json +###> symfony/phpunit-bridge ### +.phpunit.result.cache +/phpunit.xml +###< symfony/phpunit-bridge ### -/src/config/* -!/src/config/bootstrap.php -!/src/config/website.json -!/src/config/locales.json -!/src/config/themes.json -!/src/config/sphinx.json -!/src/config/memcached.json -!/src/config/database.json -!/src/config/validator.json -!/src/config/moderators.json -!/src/config/nodes.json -!/src/config/trackers.json -!/src/config/peers.json - -/src/public/sitemap.xml - -/src/storage/log/*.log - -/composer.lock - -*test* \ No newline at end of file +.vscode \ No newline at end of file diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..c933dc5 --- /dev/null +++ b/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env php +=0.3.0", - "yggverse/parser": ">=0.4.0", - "jdenticon/jdenticon": "^1.0", - "christeredvartsen/php-bittorrent": "^2.0" - }, "license": "MIT", + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "ext-ctype": "*", + "ext-iconv": "*", + "christeredvartsen/php-bittorrent": "^2.0", + "doctrine/annotations": "^2.0", + "doctrine/doctrine-bundle": "^2.10", + "doctrine/doctrine-migrations-bundle": "^3.2", + "doctrine/orm": "^2.16", + "jdenticon/jdenticon": "^1.0", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpdoc-parser": "^1.24", + "symfony/asset": "6.3.*", + "symfony/console": "6.3.*", + "symfony/crowdin-translation-provider": "6.3.*", + "symfony/doctrine-messenger": "6.3.*", + "symfony/dotenv": "6.3.*", + "symfony/expression-language": "6.3.*", + "symfony/flex": "^2", + "symfony/form": "6.3.*", + "symfony/framework-bundle": "6.3.*", + "symfony/http-client": "6.3.*", + "symfony/intl": "6.3.*", + "symfony/mailer": "6.3.*", + "symfony/mime": "6.3.*", + "symfony/monolog-bundle": "^3.0", + "symfony/notifier": "6.3.*", + "symfony/process": "6.3.*", + "symfony/property-access": "6.3.*", + "symfony/property-info": "6.3.*", + "symfony/runtime": "6.3.*", + "symfony/security-bundle": "6.3.*", + "symfony/serializer": "6.3.*", + "symfony/string": "6.3.*", + "symfony/translation": "6.3.*", + "symfony/twig-bundle": "6.3.*", + "symfony/validator": "6.3.*", + "symfony/web-link": "6.3.*", + "symfony/yaml": "6.3.*", + "twig/extra-bundle": "^2.12|^3.0", + "twig/twig": "^2.12|^3.0" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "sort-packages": true + }, "autoload": { "psr-4": { - "Yggverse\\Yggtracker\\": "src/" + "App\\": "src/" } }, - "authors": [ - { - "name": "YGGverse" + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" } - ], - "minimum-stability": "alpha" + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php73": "*", + "symfony/polyfill-php74": "*", + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*" + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "6.3.*" + } + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/browser-kit": "6.3.*", + "symfony/css-selector": "6.3.*", + "symfony/debug-bundle": "6.3.*", + "symfony/maker-bundle": "^1.0", + "symfony/phpunit-bridge": "^6.3", + "symfony/stopwatch": "6.3.*", + "symfony/web-profiler-bundle": "6.3.*" + } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2f8b33c --- /dev/null +++ b/composer.lock @@ -0,0 +1,9748 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "12e4a495651b5797393aa13acc4d132f", + "packages": [ + { + "name": "christeredvartsen/php-bittorrent", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/christeredvartsen/php-bittorrent.git", + "reference": "1e4f17ef840cd56b10e9c507df0064048fc91778" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/christeredvartsen/php-bittorrent/zipball/1e4f17ef840cd56b10e9c507df0064048fc91778", + "reference": "1e4f17ef840cd56b10e9c507df0064048fc91778", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phploc/phploc": "^5.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "BitTorrent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christer Edvartsen", + "email": "cogo@starzinger.net" + } + ], + "description": "A set of components that can be used to interact with torrent files (read+write) and classes that can encode/decode to/from the BitTorrent format.", + "homepage": "https://github.com/christeredvartsen/php-bittorrent", + "keywords": [ + "bittorrent", + "torrent" + ], + "support": { + "issues": "https://github.com/christeredvartsen/php-bittorrent/issues", + "source": "https://github.com/christeredvartsen/php-bittorrent" + }, + "time": "2020-01-21T19:12:01+00:00" + }, + { + "name": "doctrine/annotations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.1" + }, + "time": "2023-02-02T22:02:53+00:00" + }, + { + "name": "doctrine/cache", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.2.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2022-05-20T20:07:39+00:00" + }, + { + "name": "doctrine/collections", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "3023e150f90a38843856147b58190aa8b46cc155" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/3023e150f90a38843856147b58190aa8b46cc155", + "reference": "3023e150f90a38843856147b58190aa8b46cc155", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^10.0", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.1.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcollections", + "type": "tidelift" + } + ], + "time": "2023-07-06T15:15:36+00:00" + }, + { + "name": "doctrine/common", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/8b5e5650391f851ed58910b3e3d48a71062eeced", + "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0 || ^3.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0 || ^10.0", + "doctrine/collections": "^1", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^6.1", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.4.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2022-10-09T11:47:59+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3|^1", + "doctrine/event-manager": "^1|^2", + "php": "^7.4 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "12.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.35", + "phpstan/phpstan-strict-rules": "^1.5", + "phpunit/phpunit": "9.6.13", + "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.7.2", + "symfony/cache": "^5.4|^6.0", + "symfony/console": "^4.4|^5.4|^6.0", + "vimeo/psalm": "4.30.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.7.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2023-09-26T20:56:55+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" + }, + "time": "2023-09-27T20:04:15+00:00" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "2.10.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "f28b1f78de3a2938ff05cfe751233097624cc756" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/f28b1f78de3a2938ff05cfe751233097624cc756", + "reference": "f28b1f78de3a2938ff05cfe751233097624cc756", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/dbal": "^3.6.0", + "doctrine/persistence": "^2.2 || ^3", + "doctrine/sql-formatter": "^1.0.1", + "php": "^7.4 || ^8.0", + "symfony/cache": "^5.4 || ^6.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/console": "^5.4 || ^6.0", + "symfony/dependency-injection": "^5.4 || ^6.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^5.4.19 || ^6.0.7", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + }, + "conflict": { + "doctrine/annotations": ">=3.0", + "doctrine/orm": "<2.11 || >=3.0", + "twig/twig": "<1.34 || >=2.0 <2.4" + }, + "require-dev": { + "doctrine/annotations": "^1 || ^2", + "doctrine/coding-standard": "^9.0", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.11 || ^3.0", + "friendsofphp/proxy-manager-lts": "^1.0", + "phpunit/phpunit": "^9.5.26 || ^10.0", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^4", + "psr/log": "^1.1.4 || ^2.0 || ^3.0", + "symfony/phpunit-bridge": "^6.1", + "symfony/property-info": "^5.4 || ^6.0", + "symfony/proxy-manager-bridge": "^5.4 || ^6.0", + "symfony/security-bundle": "^5.4 || ^6.0", + "symfony/twig-bridge": "^5.4 || ^6.0", + "symfony/validator": "^5.4 || ^6.0", + "symfony/web-profiler-bundle": "^5.4 || ^6.0", + "symfony/yaml": "^5.4 || ^6.0", + "twig/twig": "^1.34 || ^2.12 || ^3.0", + "vimeo/psalm": "^4.30" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "ext-pdo": "*", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineBundle/issues", + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.10.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2023-08-06T09:31:40+00:00" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "94e6b0fe1a50901d52f59dbb9b4b0737718b2c1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/94e6b0fe1a50901d52f59dbb9b4b0737718b2c1e", + "reference": "94e6b0fe1a50901d52f59dbb9b4b0737718b2c1e", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "~1.0|~2.0", + "doctrine/migrations": "^3.2", + "php": "^7.2|^8.0", + "symfony/framework-bundle": "~3.4|~4.0|~5.0|~6.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "doctrine/orm": "^2.6", + "doctrine/persistence": "^1.3||^2.0", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5|^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.2.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-migrations-bundle", + "type": "tidelift" + } + ], + "time": "2023-06-02T08:19:26+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2022-10-12T20:59:15+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.8" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2023-06-16T13:40:37+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "doctrine/lexer", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-12-14T08:49:07+00:00" + }, + { + "name": "doctrine/migrations", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "e542ad8bcd606d7a18d0875babb8a6d963c9c059" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/e542ad8bcd606d7a18d0875babb8a6d963c9c059", + "reference": "e542ad8bcd606d7a18d0875babb8a6d963c9c059", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.5.1", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^4.4.16 || ^5.4 || ^6.0", + "symfony/stopwatch": "^4.4 || ^5.4 || ^6.0", + "symfony/var-exporter": "^6.2" + }, + "conflict": { + "doctrine/orm": "<2.12" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "doctrine/orm": "^2.13", + "doctrine/persistence": "^2 || ^3", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan-symfony": "^1.1", + "phpunit/phpunit": "^9.5.24", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "symfony/process": "^4.4 || ^5.4 || ^6.0", + "symfony/yaml": "^4.4 || ^5.4 || ^6.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.6.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2023-02-15T18:49:46+00:00" + }, + { + "name": "doctrine/orm", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/17500f56eaa930f5cd14d765bc2cd851c7d37cc0", + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5 || ^2.1", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3 || ^2", + "doctrine/lexer": "^2", + "doctrine/persistence": "^2.4 || ^3", + "ext-ctype": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^4.2 || ^5.0 || ^6.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 3.0" + }, + "require-dev": { + "doctrine/annotations": "^1.13 || ^2", + "doctrine/coding-standard": "^9.0.2 || ^12.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "~1.4.10 || 1.10.28", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "psr/log": "^1 || ^2 || ^3", + "squizlabs/php_codesniffer": "3.7.2", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", + "vimeo/psalm": "4.30.0 || 5.14.1" + }, + "suggest": { + "ext-dom": "Provides support for XSD validation for XML mapping files", + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.16.2" + }, + "time": "2023-08-27T18:21:56+00:00" + }, + { + "name": "doctrine/persistence", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "63fee8c33bef740db6730eb2a750cd3da6495603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/63fee8c33bef740db6730eb2a750cd3da6495603", + "reference": "63fee8c33bef740db6730eb2a750cd3da6495603", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^7.2 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/coding-standard": "^11", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.9.4", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.30.0 || 5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/3.2.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2023-05-17T18:32:04+00:00" + }, + { + "name": "doctrine/sql-formatter", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "25a06c7bf4c6b8218f47928654252863ffc890a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/25a06c7bf4c6b8218f47928654252863ffc890a5", + "reference": "25a06c7bf4c6b8218f47928654252863ffc890a5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.1.3" + }, + "time": "2022-05-23T21:33:49+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^4.30" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2023-01-14T14:17:03+00:00" + }, + { + "name": "jdenticon/jdenticon", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dmester/jdenticon-php.git", + "reference": "cabb7a44c413c318392a341c5d3ca30fcdd57a6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dmester/jdenticon-php/zipball/cabb7a44c413c318392a341c5d3ca30fcdd57a6f", + "reference": "cabb7a44c413c318392a341c5d3ca30fcdd57a6f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jdenticon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Mester Pirttijärvi" + } + ], + "description": "Render PNG and SVG identicons.", + "homepage": "https://jdenticon.com/", + "keywords": [ + "avatar", + "identicon", + "jdenticon" + ], + "support": { + "docs": "https://jdenticon.com/php-api.html", + "issues": "https://github.com/dmester/jdenticon-php/issues", + "source": "https://github.com/dmester/jdenticon-php" + }, + "time": "2022-10-30T17:15:02+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.4.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-06-21T08:46:11+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.7.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" + }, + "time": "2023-08-12T11:01:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.24.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "bcad8d995980440892759db0c32acae7c8e79442" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", + "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" + }, + "time": "2023-09-26T12:28:12+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/link", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/link.git", + "reference": "84b159194ecfd7eaa472280213976e96415433f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/link/zipball/84b159194ecfd7eaa472280213976e96415433f7", + "reference": "84b159194ecfd7eaa472280213976e96415433f7", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "suggest": { + "fig/link-util": "Provides some useful PSR-13 utilities" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Link\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for HTTP links", + "homepage": "https://github.com/php-fig/link", + "keywords": [ + "http", + "http-link", + "link", + "psr", + "psr-13", + "rest" + ], + "support": { + "source": "https://github.com/php-fig/link/tree/2.0.1" + }, + "time": "2021-03-11T23:00:27+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/asset", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "b77a4cc8e266b7e0db688de740f9ee7253aa411c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/b77a4cc8e266b7e0db688de740f9ee7253aa411c", + "reference": "b77a4cc8e266b7e0db688de740f9ee7253aa411c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "symfony/http-foundation": "<5.4" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-21T14:41:17+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "6c1a3ea078c4d88ee892530945df63a87981b2da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/6c1a3ea078c4d88ee892530945df63a87981b2da", + "reference": "6c1a3ea078c4d88ee892530945df63a87981b2da", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.2.10" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-26T15:48:55+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/clock", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "a74086d3db70d0f06ffd84480daa556248706e98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/a74086d3db70d0f06ffd84480daa556248706e98", + "reference": "a74086d3db70d0f06ffd84480daa556248706e98", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-31T11:35:03+00:00" + }, + { + "name": "symfony/config", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-19T20:22:16+00:00" + }, + { + "name": "symfony/console", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6", + "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T10:10:12+00:00" + }, + { + "name": "symfony/crowdin-translation-provider", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/crowdin-translation-provider.git", + "reference": "dea8d5a664780e9c36efc075fa758d435e4ef3a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/crowdin-translation-provider/zipball/dea8d5a664780e9c36efc075fa758d435e4ef3a7", + "reference": "dea8d5a664780e9c36efc075fa758d435e4ef3a7", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/config": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation": "^5.4.21|^6.2.7" + }, + "type": "symfony-translation-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\Bridge\\Crowdin\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrii Bodnar", + "homepage": "https://github.com/andrii-bodnar" + }, + { + "name": "Mathieu Santostefano", + "homepage": "https://github.com/welcomattic" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Crowdin Translation Provider Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "crowdin", + "provider", + "translation" + ], + "support": { + "source": "https://github.com/symfony/crowdin-translation-provider/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-20T13:15:31+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "2ed62b3bf98346e1f45529a7b6be2196739bb993" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2ed62b3bf98346e1f45529a7b6be2196739bb993", + "reference": "2ed62b3bf98346e1f45529a7b6be2196739bb993", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1", + "symfony/expression-language": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-25T16:46:40+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "9977eb1adf999ceded213e88c1ac6dff7a1a0306" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/9977eb1adf999ceded213e88c1ac6dff7a1a0306", + "reference": "9977eb1adf999ceded213e88c1ac6dff7a1a0306", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1.2|^2", + "doctrine/persistence": "^2|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.13.1", + "doctrine/dbal": "<2.13.1", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.12", + "symfony/cache": "<5.4", + "symfony/dependency-injection": "<6.2", + "symfony/form": "<5.4.21|>=6,<6.2.7", + "symfony/http-foundation": "<6.3", + "symfony/http-kernel": "<6.2", + "symfony/lock": "<6.3", + "symfony/messenger": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-bundle": "<5.4", + "symfony/security-core": "<6.0", + "symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.1" + }, + "require-dev": { + "doctrine/annotations": "^1.13.1|^2", + "doctrine/collections": "^1.0|^2.0", + "doctrine/data-fixtures": "^1.1", + "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/orm": "^2.12", + "psr/log": "^1|^2|^3", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", + "symfony/doctrine-messenger": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4.21|^6.2.7", + "symfony/http-kernel": "^6.3", + "symfony/lock": "^6.3", + "symfony/messenger": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/proxy-manager-bridge": "^5.4|^6.0", + "symfony/security-core": "^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4.25|~6.2.12|^6.3.1", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Doctrine with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T16:16:03+00:00" + }, + { + "name": "symfony/doctrine-messenger", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-messenger.git", + "reference": "f1c253e24ae6d2bc4939b1439e074e6d2e73ecdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/f1c253e24ae6d2bc4939b1439e074e6d2e73ecdb", + "reference": "f1c253e24ae6d2bc4939b1439e074e6d2e73ecdb", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^2.13|^3.0", + "php": ">=8.1", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/persistence": "<1.3" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "symfony/property-access": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "type": "symfony-messenger-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Doctrine Messenger Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-messenger/tree/v6.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-06-24T11:51:27+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "ceadb434fe2a6763a03d2d110441745834f3dd1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/ceadb434fe2a6763a03d2d110441745834f3dd1e", + "reference": "ceadb434fe2a6763a03d2d110441745834f3dd1e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-21T14:41:17+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "1f69476b64fb47105c06beef757766c376b548c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/1f69476b64fb47105c06beef757766c376b548c4", + "reference": "1f69476b64fb47105c06beef757766c376b548c4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-12T06:57:20+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-06T06:56:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "6d560c4c80e7e328708efd923f93ad67e6a0c1c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/6d560c4c80e7e328708efd923f93ad67e6a0c1c0", + "reference": "6d560c4c80e7e328708efd923f93ad67e6a0c1c0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-28T16:05:33+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-06-01T08:30:39+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/a1b31d88c0e998168ca7792f222cbecee47428c4", + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-26T12:56:25+00:00" + }, + { + "name": "symfony/flex", + "version": "v2.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "9c402af768c6c9f8126a9ffa192ecf7c16581e35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/9c402af768c6c9f8126a9ffa192ecf7c16581e35", + "reference": "9c402af768c6c9f8126a9ffa192ecf7c16581e35", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.0" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-04T09:02:35+00:00" + }, + { + "name": "symfony/form", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/form.git", + "reference": "0f9ad8600c1021983d096512066ee54332aa3139" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/form/zipball/0f9ad8600c1021983d096512066ee54332aa3139", + "reference": "0f9ad8600c1021983d096512066ee54332aa3139", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/options-resolver": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/doctrine-bridge": "<5.4.21|>=6,<6.2.7", + "symfony/error-handler": "<5.4", + "symfony/framework-bundle": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.3" + }, + "require-dev": { + "doctrine/collections": "^1.0|^2.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/security-core": "^6.2", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Form\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows to easily create, process and reuse HTML forms", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/form/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-10T17:47:23+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "567cafcfc08e3076b47290a7558b0ca17a98b0ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/567cafcfc08e3076b47290a7558b0ca17a98b0ce", + "reference": "567cafcfc08e3076b47290a7558b0ca17a98b0ce", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.3.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^5.4|^6.0" + }, + "conflict": { + "doctrine/annotations": "<1.13.1", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<5.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<6.3", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.2", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.3", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.2.8", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.3", + "symfony/web-profiler-bundle": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13.1|^2", + "doctrine/persistence": "^1.3|^2|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^6.3", + "symfony/dotenv": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-client": "^6.3", + "symfony/lock": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^6.3", + "symfony/mime": "^6.2", + "symfony/notifier": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/scheduler": "^6.3", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/semaphore": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^6.2.8", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/twig": "^2.10|^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T10:45:15+00:00" + }, + { + "name": "symfony/http-client", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/213e564da4cbf61acc9728d97e666bcdb868c10d", + "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T15:57:12+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", + "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b50f5e281d722cb0f4c296f908bacc3e2b721957", + "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.2" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-04T21:33:54+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f991a964368bee8d883e8d57ced4fe9fff04dfc", + "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^6.3.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.3.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "symfony/var-dumper": "<6.3", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.3.4", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4.5|^6.0.5", + "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/var-exporter": "^6.2", + "twig/twig": "^2.13|^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-30T06:37:04+00:00" + }, + { + "name": "symfony/intl", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", + "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides access to the localization data of the ICU library", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "support": { + "source": "https://github.com/symfony/intl/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-20T07:43:09+00:00" + }, + { + "name": "symfony/mailer", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d89611a7830d51b5e118bca38e390dea92f9ea06", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^6.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/messenger": "^6.2", + "symfony/twig-bridge": "^6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-06T09:47:15+00:00" + }, + { + "name": "symfony/messenger", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/messenger.git", + "reference": "fb29632c2abdc52f4f10f9402f8f93ebb8938234" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/messenger/zipball/fb29632c2abdc52f4f10f9402f8f93ebb8938234", + "reference": "fb29632c2abdc52f4f10f9402f8f93ebb8938234", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/clock": "^6.3" + }, + "conflict": { + "symfony/console": "<6.3", + "symfony/event-dispatcher": "<5.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/framework-bundle": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/serializer": "<5.4" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/console": "^6.3", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Samuel Roze", + "email": "samuel.roze@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps applications send and receive messages to/from other applications or via message queues", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/messenger/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T16:11:24+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/d5179eedf1cb2946dbd760475ebf05c251ef6a6e", + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.2.13|>=6.3,<6.3.2" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "~6.2.13|^6.3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T06:59:36+00:00" + }, + { + "name": "symfony/monolog-bridge", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "04b04b8e465e0fa84940e5609b6796a8b4e51bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/04b04b8e465e0fa84940e5609b6796a8b4e51bf1", + "reference": "04b04b8e465e0fa84940e5609b6796a8b4e51bf1", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.25.1|^2|^3", + "php": ">=8.1", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/http-foundation": "<5.4", + "symfony/security-core": "<6.0" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/security-core": "^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Monolog\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Monolog with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/monolog-bridge/tree/v6.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-06-08T11:13:32+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d", + "reference": "a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.22 || ^2.0 || ^3.0", + "php": ">=7.1.3", + "symfony/config": "~4.4 || ^5.0 || ^6.0", + "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "~4.4 || ^5.0 || ^6.0", + "symfony/monolog-bridge": "~4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "symfony/console": "~4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.2 || ^6.0", + "symfony/yaml": "~4.4 || ^5.0 || ^6.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony MonologBundle", + "homepage": "https://symfony.com", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/symfony/monolog-bundle/issues", + "source": "https://github.com/symfony/monolog-bundle/tree/v3.8.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T14:24:36+00:00" + }, + { + "name": "symfony/notifier", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/notifier.git", + "reference": "a30aee1bf767835d7948138c1629e310cee26a8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/notifier/zipball/a30aee1bf767835d7948138c1629e310cee26a8b", + "reference": "a30aee1bf767835d7948138c1629e310cee26a8b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/event-dispatcher": "<5.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4" + }, + "require-dev": { + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Sends notifications via one or more channels (email, SMS, ...)", + "homepage": "https://symfony.com", + "keywords": [ + "notification", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/notifier/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-12T10:17:15+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a10f19f5198d589d5c33333cffe98dc9820332dd", + "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-12T14:21:09+00:00" + }, + { + "name": "symfony/password-hasher", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/password-hasher.git", + "reference": "278d3a49715073879f75e372ad80b8cfeca949d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/278d3a49715073879f75e372ad80b8cfeca949d3", + "reference": "278d3a49715073879f75e372ad80b8cfeca949d3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "symfony/security-core": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/security-core": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PasswordHasher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides password hashing utilities", + "homepage": "https://symfony.com", + "keywords": [ + "hashing", + "password" + ], + "support": { + "source": "https://github.com/symfony/password-hasher/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-25T17:05:16+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "875e90aeea2777b6f135677f618529449334a612" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "e46b4da57951a16053cd751f63f4a24292788157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e46b4da57951a16053cd751f63f4a24292788157", + "reference": "e46b4da57951a16053cd751f63f4a24292788157", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Icu\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-21T17:27:24+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:30:37+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T06:22:46+00:00" + }, + { + "name": "symfony/process", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54", + "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-07T10:39:22+00:00" + }, + { + "name": "symfony/property-access", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/2dc4f9da444b8f8ff592e95d570caad67924f1d0", + "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/property-info": "^5.4|^6.0" + }, + "require-dev": { + "symfony/cache": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-13T15:26:11+00:00" + }, + { + "name": "symfony/property-info", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd", + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-19T08:06:44+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/82616e59acd3e3d9c916bba798326cb7796d7d31", + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-20T16:05:51+00:00" + }, + { + "name": "symfony/runtime", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "d5c09493647a0c1a16e6c8da308098e840d1164f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/d5c09493647a0c1a16e6c8da308098e840d1164f", + "reference": "d5c09493647a0c1a16e6c8da308098e840d1164f", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.1" + }, + "conflict": { + "symfony/dotenv": "<5.4" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/dotenv": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-16T17:05:46+00:00" + }, + { + "name": "symfony/security-bundle", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-bundle.git", + "reference": "2df460eacceb11b9287cfafddda4d27023dd9001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/2df460eacceb11b9287cfafddda4d27023dd9001", + "reference": "2df460eacceb11b9287cfafddda4d27023dd9001", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.1", + "symfony/clock": "^6.3", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^6.2", + "symfony/http-kernel": "^6.2", + "symfony/password-hasher": "^5.4|^6.0", + "symfony/security-core": "^6.2", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/security-http": "^6.3.4" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/console": "<5.4", + "symfony/framework-bundle": "<6.3", + "symfony/http-client": "<5.4", + "symfony/ldap": "<5.4", + "symfony/twig-bundle": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "symfony/asset": "^5.4|^6.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^6.3", + "symfony/http-client": "^5.4|^6.0", + "symfony/ldap": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/twig-bridge": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1", + "web-token/jwt-signature-algorithm-eddsa": "^3.1", + "web-token/jwt-signature-algorithm-hmac": "^3.1", + "web-token/jwt-signature-algorithm-none": "^3.1", + "web-token/jwt-signature-algorithm-rsa": "^3.1" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\SecurityBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-bundle/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-25T17:05:55+00:00" + }, + { + "name": "symfony/security-core", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "ec8f24dc1195f46483510892271d01a5202bba70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/ec8f24dc1195f46483510892271d01a5202bba70", + "reference": "ec8f24dc1195f46483510892271d01a5202bba70", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/password-hasher": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/event-dispatcher": "<5.4", + "symfony/http-foundation": "<5.4", + "symfony/ldap": "<5.4", + "symfony/security-guard": "<5.4", + "symfony/validator": "<5.4" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "psr/container": "^1.1|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/ldap": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Core\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - Core Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-core/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-10T17:47:23+00:00" + }, + { + "name": "symfony/security-csrf", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-csrf.git", + "reference": "63d7b098c448cbddb46ea5eda33b68c1ece6eb5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/63d7b098c448cbddb46ea5eda33b68c1ece6eb5b", + "reference": "63d7b098c448cbddb46ea5eda33b68c1ece6eb5b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/security-core": "^5.4|^6.0" + }, + "conflict": { + "symfony/http-foundation": "<5.4" + }, + "require-dev": { + "symfony/http-foundation": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Csrf\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - CSRF Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-csrf/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-05T08:41:27+00:00" + }, + { + "name": "symfony/security-http", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-http.git", + "reference": "47058ea557a4c64ba86e9249651222842bd52e2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-http/zipball/47058ea557a4c64ba86e9249651222842bd52e2a", + "reference": "47058ea557a4c64ba86e9249651222842bd52e2a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/security-core": "^6.3" + }, + "conflict": { + "symfony/clock": "<6.3", + "symfony/event-dispatcher": "<5.4.9|>=6,<6.0.9", + "symfony/http-client-contracts": "<3.0", + "symfony/security-bundle": "<5.4", + "symfony/security-csrf": "<5.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/cache": "^5.4|^6.0", + "symfony/clock": "^6.3", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Http\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - HTTP Integration", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-http/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-30T06:30:46+00:00" + }, + { + "name": "symfony/serializer", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "855fc058c8bdbb69f53834f2fdb3876c9bc0ab7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/855fc058c8bdbb69f53834f2fdb3876c9bc0ab7c", + "reference": "855fc058c8bdbb69f53834f2fdb3876c9bc0ab7c", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<5.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4.24|>=6,<6.2.11", + "symfony/uid": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4.24|^6.2.11", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T16:18:53+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-16T10:14:28+00:00" + }, + { + "name": "symfony/string", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339", + "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-18T10:38:32+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", + "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-31T07:08:24+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-30T17:17:10+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "18f2cbe1d46ad43c4d3bd45e5e6279172068e064" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/18f2cbe1d46ad43c4d3bd45e5e6279172068e064", + "reference": "18f2cbe1d46ad43c4d3bd45e5e6279172068e064", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.2", + "symfony/mime": "<6.2", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^6.3", + "symfony/html-sanitizer": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/intl": "^5.4|^6.0", + "symfony/mime": "^6.2", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^5.4|^6.0", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/security-http": "^5.4|^6.0", + "symfony/serializer": "^6.2", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^6.1", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-12T06:57:20+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", + "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "symfony/asset": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/web-link": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-06T09:53:41+00:00" + }, + { + "name": "symfony/validator", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", + "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.13", + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<5.4", + "symfony/expression-language": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/intl": "<5.4", + "symfony/property-info": "<5.4", + "symfony/translation": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13|^2", + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T07:41:15+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-12T10:11:35+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "df1f8aac5751871b83d30bf3e2c355770f8f0691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/df1f8aac5751871b83d30bf3e2c355770f8f0691", + "reference": "df1f8aac5751871b83d30bf3e2c355770f8f0691", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T18:14:47+00:00" + }, + { + "name": "symfony/web-link", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-link.git", + "reference": "0989ca617d0703cdca501a245f10e194ff22315b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-link/zipball/0989ca617d0703cdca501a245f10e194ff22315b", + "reference": "0989ca617d0703cdca501a245f10e194ff22315b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/link": "^1.1|^2.0" + }, + "conflict": { + "symfony/http-kernel": "<5.4" + }, + "provide": { + "psr/link-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/http-kernel": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\WebLink\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Manages links between resources", + "homepage": "https://symfony.com", + "keywords": [ + "dns-prefetch", + "http", + "http2", + "link", + "performance", + "prefetch", + "preload", + "prerender", + "psr13", + "push" + ], + "support": { + "source": "https://github.com/symfony/web-link/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-21T14:41:17+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e23292e8c07c85b971b44c1c4b87af52133e2add", + "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-31T07:08:24+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3", + "reference": "f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "twig/twig": "^2.7|^3.0" + }, + "require-dev": { + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4|^6.3", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^2.12|^3.0", + "twig/html-extra": "^2.12|^3.0", + "twig/inky-extra": "^2.12|^3.0", + "twig/intl-extra": "^2.12|^3.0", + "twig/markdown-extra": "^2.12|^3.0", + "twig/string-extra": "^2.12|^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-07-29T15:34:56+00:00" + }, + { + "name": "twig/twig", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-08-28T11:09:02+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "masterminds/html5", + "version": "2.8.1", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", + "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" + }, + "time": "2023-05-10T11:58:31+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.17.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + }, + "time": "2023-08-13T19:53:39+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.29", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-09-19T04:57:46+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-09-19T05:39:22+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "ca4a988488f61ac18f8f845445eabdd36f89aa8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ca4a988488f61ac18f8f845445eabdd36f89aa8d", + "reference": "ca4a988488f61ac18f8f845445eabdd36f89aa8d", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dom-crawler": "^5.4|^6.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-06T06:56:43+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "883d961421ab1709877c10ac99451632a3d6fa57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57", + "reference": "883d961421ab1709877c10ac99451632a3d6fa57", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-12T16:00:22+00:00" + }, + { + "name": "symfony/debug-bundle", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "3f04a578e1a9f1d7da84a87b690c03123e5d8c31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/3f04a578e1a9f1d7da84a87b690c03123e5d8c31", + "reference": "3f04a578e1a9f1d7da84a87b690c03123e5d8c31", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": ">=8.1", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/twig-bridge": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/dependency-injection": "<5.4" + }, + "require-dev": { + "symfony/config": "^5.4|^6.0", + "symfony/web-profiler-bundle": "^5.4|^6.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\DebugBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug-bundle/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-13T14:29:38+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "3fdd2a3d5fdc363b2e8dbf817f9726a4d013cbd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/3fdd2a3d5fdc363b2e8dbf817f9726a4d013cbd1", + "reference": "3fdd2a3d5fdc363b2e8dbf817f9726a4d013cbd1", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-01T07:43:40+00:00" + }, + { + "name": "symfony/maker-bundle", + "version": "v1.51.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/maker-bundle.git", + "reference": "0890fd3cf1e2a5221f9b3c6ee1769c537aef683d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/0890fd3cf1e2a5221f9b3c6ee1769c537aef683d", + "reference": "0890fd3cf1e2a5221f9b3c6ee1769c537aef683d", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "nikic/php-parser": "^4.11", + "php": ">=8.1", + "symfony/config": "^6.3|^7.0", + "symfony/console": "^6.3|^7.0", + "symfony/dependency-injection": "^6.3|^7.0", + "symfony/deprecation-contracts": "^2.2|^3", + "symfony/filesystem": "^6.3|^7.0", + "symfony/finder": "^6.3|^7.0", + "symfony/framework-bundle": "^6.3|^7.0", + "symfony/http-kernel": "^6.3|^7.0", + "symfony/process": "^6.3|^7.0" + }, + "conflict": { + "doctrine/doctrine-bundle": "<2.4", + "doctrine/orm": "<2.10" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/doctrine-bundle": "^2.5.0", + "doctrine/orm": "^2.10.0", + "symfony/http-client": "^6.3|^7.0", + "symfony/phpunit-bridge": "^6.3|^7.0", + "symfony/security-core": "^6.3|^7.0", + "symfony/yaml": "^6.3|^7.0", + "twig/twig": "^2.0|^3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MakerBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", + "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", + "keywords": [ + "code generator", + "dev", + "generator", + "scaffold", + "scaffolding" + ], + "support": { + "issues": "https://github.com/symfony/maker-bundle/issues", + "source": "https://github.com/symfony/maker-bundle/tree/v1.51.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-18T18:17:31+00:00" + }, + { + "name": "symfony/phpunit-bridge", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "e020e1efbd1b42cb670fcd7d19a25abbddba035d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e020e1efbd1b42cb670fcd7d19a25abbddba035d", + "reference": "e020e1efbd1b42cb670fcd7d19a25abbddba035d", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/polyfill-php81": "^1.27" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "thanks": { + "name": "phpunit/phpunit", + "url": "https://github.com/sebastianbergmann/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/phpunit-bridge/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-12T16:00:22+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "6101b5ab7857c373d237e121f9060c68b32e1373" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/6101b5ab7857c373d237e121f9060c68b32e1373", + "reference": "6101b5ab7857c373d237e121f9060c68b32e1373", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/config": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/http-kernel": "^6.3", + "symfony/routing": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/form": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4" + }, + "require-dev": { + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\WebProfilerBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a development tool that gives detailed information about the execution of any request", + "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], + "support": { + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-19T20:17:28+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.1", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/bundles.php b/config/bundles.php new file mode 100644 index 0000000..0457f99 --- /dev/null +++ b/config/bundles.php @@ -0,0 +1,14 @@ + ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], +]; diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml new file mode 100644 index 0000000..6899b72 --- /dev/null +++ b/config/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Unique name of your app: used to compute stable namespaces for cache keys. + #prefix_seed: your_vendor_name/app_name + + # The "app" cache stores to the filesystem by default. + # The data in this cache should persist between deploys. + # Other options include: + + # Redis + #app: cache.adapter.redis + #default_redis_provider: redis://localhost + + # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) + #app: cache.adapter.apcu + + # Namespaced pools use the above "app" backend by default + #pools: + #my.dedicated.cache: null diff --git a/config/packages/debug.yaml b/config/packages/debug.yaml new file mode 100644 index 0000000..ad874af --- /dev/null +++ b/config/packages/debug.yaml @@ -0,0 +1,5 @@ +when@dev: + debug: + # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. + # See the "server:dump" command to start a new server. + dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml new file mode 100644 index 0000000..ec0f77e --- /dev/null +++ b/config/packages/doctrine.yaml @@ -0,0 +1,48 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '15' + + profiling_collect_backtrace: '%kernel.debug%' + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + App: + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/config/packages/doctrine_migrations.yaml b/config/packages/doctrine_migrations.yaml new file mode 100644 index 0000000..29231d9 --- /dev/null +++ b/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml new file mode 100644 index 0000000..6d85c29 --- /dev/null +++ b/config/packages/framework.yaml @@ -0,0 +1,25 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + #csrf_protection: true + http_method_override: false + handle_all_throwables: true + + # Enables session support. Note that the session will ONLY be started if you read or write from it. + # Remove or comment this section to explicitly disable session support. + session: + handler_id: null + cookie_secure: auto + cookie_samesite: lax + storage_factory_id: session.storage.factory.native + + #esi: true + #fragments: true + php_errors: + log: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/config/packages/mailer.yaml b/config/packages/mailer.yaml new file mode 100644 index 0000000..56a650d --- /dev/null +++ b/config/packages/mailer.yaml @@ -0,0 +1,3 @@ +framework: + mailer: + dsn: '%env(MAILER_DSN)%' diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml new file mode 100644 index 0000000..587083a --- /dev/null +++ b/config/packages/messenger.yaml @@ -0,0 +1,24 @@ +framework: + messenger: + failure_transport: failed + + transports: + # https://symfony.com/doc/current/messenger.html#transport-configuration + async: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + options: + use_notify: true + check_delayed_interval: 60000 + retry_strategy: + max_retries: 3 + multiplier: 2 + failed: 'doctrine://default?queue_name=failed' + # sync: 'sync://' + + routing: + Symfony\Component\Mailer\Messenger\SendEmailMessage: async + Symfony\Component\Notifier\Message\ChatMessage: async + Symfony\Component\Notifier\Message\SmsMessage: async + + # Route your messages to the transports + # 'App\Message\YourMessage': async diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml new file mode 100644 index 0000000..8c9efa9 --- /dev/null +++ b/config/packages/monolog.yaml @@ -0,0 +1,61 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + +when@dev: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +when@prod: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + buffer_size: 50 # How many messages should be saved? Prevent memory leaks + nested: + type: stream + path: php://stderr + level: debug + formatter: monolog.formatter.json + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine"] + deprecation: + type: stream + channels: [deprecation] + path: php://stderr diff --git a/config/packages/notifier.yaml b/config/packages/notifier.yaml new file mode 100644 index 0000000..7f4bed1 --- /dev/null +++ b/config/packages/notifier.yaml @@ -0,0 +1,13 @@ +framework: + notifier: + chatter_transports: + texter_transports: + crowdin: '%env(CROWDIN_DSN)%' + channel_policy: + # use chat/slack, chat/telegram, sms/twilio or sms/nexmo + urgent: ['email'] + high: ['email'] + medium: ['email'] + low: ['email'] + admin_recipients: + - { email: admin@example.com } diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml new file mode 100644 index 0000000..4b766ce --- /dev/null +++ b/config/packages/routing.yaml @@ -0,0 +1,12 @@ +framework: + router: + utf8: true + + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/config/packages/security.yaml b/config/packages/security.yaml new file mode 100644 index 0000000..367af25 --- /dev/null +++ b/config/packages/security.yaml @@ -0,0 +1,39 @@ +security: + # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords + password_hashers: + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: + users_in_memory: { memory: null } + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + provider: users_in_memory + + # activate different ways to authenticate + # https://symfony.com/doc/current/security.html#the-firewall + + # https://symfony.com/doc/current/security/impersonating_user.html + # switch_user: true + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } + +when@test: + security: + password_hashers: + # By default, password hashers are resource intensive and take time. This is + # important to generate secure password hashes. In tests however, secure hashes + # are not important, waste resources and increase test times. The following + # reduces the work factor to the lowest possible values. + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: + algorithm: auto + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml new file mode 100644 index 0000000..888f0ba --- /dev/null +++ b/config/packages/translation.yaml @@ -0,0 +1,15 @@ +framework: + default_locale: en + translator: + default_path: '%kernel.project_dir%/translations' + fallbacks: + - en +# providers: +# crowdin: +# dsn: '%env(CROWDIN_DSN)%' +# loco: +# dsn: '%env(LOCO_DSN)%' +# lokalise: +# dsn: '%env(LOKALISE_DSN)%' +# phrase: +# dsn: '%env(PHRASE_DSN)%' diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml new file mode 100644 index 0000000..8052606 --- /dev/null +++ b/config/packages/twig.yaml @@ -0,0 +1,9 @@ +twig: + default_path: '%kernel.project_dir%/templates' + globals: + version: '%app.version%' + name: '%app.name%' + +when@test: + twig: + strict_variables: true diff --git a/config/packages/validator.yaml b/config/packages/validator.yaml new file mode 100644 index 0000000..0201281 --- /dev/null +++ b/config/packages/validator.yaml @@ -0,0 +1,13 @@ +framework: + validation: + email_validation_mode: html5 + + # Enables validator auto-mapping support. + # For instance, basic validation constraints will be inferred from Doctrine's metadata. + #auto_mapping: + # App\Entity\: [] + +when@test: + framework: + validation: + not_compromised_password: false diff --git a/config/packages/web_profiler.yaml b/config/packages/web_profiler.yaml new file mode 100644 index 0000000..b946111 --- /dev/null +++ b/config/packages/web_profiler.yaml @@ -0,0 +1,17 @@ +when@dev: + web_profiler: + toolbar: true + intercept_redirects: false + + framework: + profiler: + only_exceptions: false + collect_serializer_data: true + +when@test: + web_profiler: + toolbar: false + intercept_redirects: false + + framework: + profiler: { collect: false } diff --git a/config/preload.php b/config/preload.php new file mode 100644 index 0000000..5ebcdb2 --- /dev/null +++ b/config/preload.php @@ -0,0 +1,5 @@ +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 Y?O^L-=_T)|24>0Ra=}vH$=8 diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..f4f0a6c --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,14 @@ +version: '3' + +services: +###> doctrine/doctrine-bundle ### + database: + ports: + - "5432" +###< doctrine/doctrine-bundle ### + +###> symfony/mailer ### + mailer: + image: schickling/mailcatcher + ports: ["1025", "1080"] +###< symfony/mailer ### diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1067b9c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3' + +services: +###> doctrine/doctrine-bundle ### + database: + image: postgres:${POSTGRES_VERSION:-15}-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + # You should definitely change the password in production + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_USER: ${POSTGRES_USER:-app} + volumes: + - database_data:/var/lib/postgresql/data:rw + # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! + # - ./docker/db/data:/var/lib/postgresql/data:rw +###< doctrine/doctrine-bundle ### + +volumes: +###> doctrine/doctrine-bundle ### + database_data: +###< doctrine/doctrine-bundle ### diff --git a/example/environment/crontab b/example/environment/crontab deleted file mode 100644 index 136ec32..0000000 --- a/example/environment/crontab +++ /dev/null @@ -1,10 +0,0 @@ -@reboot searchd -@reboot indexer --all --rotate - -* * * * * indexer magnet --rotate > /dev/null 2>&1 - -* * * * * /usr/bin/php /YGGtracker/src/crontab/scrape.php > /dev/null 2>&1 -* * * * * /usr/bin/php /YGGtracker/src/crontab/export/push.php > /dev/null 2>&1 -0 5 * * * /usr/bin/php /YGGtracker/src/crontab/import/feed.php > /dev/null 2>&1 -0 0 * * * /usr/bin/php /YGGtracker/src/crontab/export/feed.php > /dev/null 2>&1 -0 0 * * * /usr/bin/php /YGGtracker/src/crontab/sitemap.php > /dev/null 2>&1 \ No newline at end of file diff --git a/example/environment/nginx b/example/environment/nginx deleted file mode 100644 index c5a243f..0000000 --- a/example/environment/nginx +++ /dev/null @@ -1,29 +0,0 @@ -server { - listen [::]:80 default; - - allow 0200::/7; - deny all; - - root /var/www/html; - - index index.html index.htm index.nginx-debian.html index.php; - - server_name _; - - location / { - try_files $uri $uri/ =404 @yggtracker; - } - - location ~ \.php$ { - include snippets/fastcgi-php.conf; - fastcgi_pass unix:/run/php/php8.1-fpm.sock; - } - - location ~ /\. { - deny all; - } - - location @yggtracker { - rewrite ^/(.*)$ /index.php?$1 last; - } -} \ No newline at end of file diff --git a/example/environment/sphinx.conf b/example/environment/sphinx.conf deleted file mode 100644 index b67c6a4..0000000 --- a/example/environment/sphinx.conf +++ /dev/null @@ -1,71 +0,0 @@ -source yggtracker -{ - type = mysql - - sql_port = 3306 - sql_host = localhost - sql_user = - sql_pass = - sql_db = -} - -source magnet : yggtracker -{ - sql_query = \ - SELECT `magnet`.`timeAdded`, \ - `magnet`.`timeUpdated`, \ - `magnet`.`magnetId`, \ - `magnet`.`title`, \ - `magnet`.`preview`, \ - `magnet`.`description`, \ - `magnet`.`dn`, \ - (SELECT GROUP_CONCAT(DISTINCT `infoHash`.`value`) \ - FROM `infoHash` \ - JOIN `magnetToInfoHash` ON (`magnetToInfoHash`.`magnetId` = `magnet`.`magnetId`) \ - WHERE `infoHash`.`infoHashId` = `magnetToInfoHash`.`infoHashId`) AS `infoHash`, \ - (SELECT GROUP_CONCAT(DISTINCT `keywordTopic`.`value`) \ - FROM `keywordTopic` \ - JOIN `magnetToKeywordTopic` ON (`magnetToKeywordTopic`.`magnetId` = `magnet`.`magnetId`) \ - WHERE `keywordTopic`.`keywordTopicId` = `magnetToKeywordTopic`.`keywordTopicId`) AS `keywords`, \ - (SELECT GROUP_CONCAT(DISTINCT `magnetComment`.`value`) \ - FROM `magnetComment` \ - WHERE `magnetComment`.`magnetId` = `magnet`.`magnetId`) AS `comments` \ - FROM `magnet`\ - - sql_attr_uint = magnetId -} - -index magnet -{ - source = magnet - path = /var/lib/sphinxsearch/data/magnet - - morphology = stem_cz, stem_ar, stem_enru - - min_word_len = 2 - min_prefix_len = 2 - - html_strip = 1 - - index_exact_words = 1 -} - -indexer -{ - mem_limit = 256M -} - -searchd -{ - listen = 127.0.0.1:9306:mysql41 - log = /var/log/sphinxsearch/searchd.log - query_log = /var/log/sphinxsearch/query.log - pid_file = /run/sphinxsearch/searchd.pid - binlog_path = /var/lib/sphinxsearch/data - read_timeout = 5 - max_children = 30 - seamless_rotate = 1 - preopen_indexes = 1 - unlink_old = 1 - workers = threads # for RT to work -} \ No newline at end of file diff --git a/src/storage/log/index.html b/migrations/.gitignore similarity index 100% rename from src/storage/log/index.html rename to migrations/.gitignore diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..6c4bfed --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + tests + + + + + + src + + + + + + + + + + diff --git a/src/public/assets/theme/default/css/common.css b/public/asset/default/css/common.css similarity index 100% rename from src/public/assets/theme/default/css/common.css rename to public/asset/default/css/common.css diff --git a/src/public/assets/theme/default/css/framework.css b/public/asset/default/css/framework.css similarity index 88% rename from src/public/assets/theme/default/css/framework.css rename to public/asset/default/css/framework.css index ea13706..65e4289 100644 --- a/src/public/assets/theme/default/css/framework.css +++ b/public/asset/default/css/framework.css @@ -122,19 +122,19 @@ a.label-green:hover { vertical-align: middle; } -.top--2 { +.top--2-px { top: -2px; } -.top-2 { +.top-2-px { top: 2px; } -.line-height-26 { +.line-height-26-px { line-height: 26px; } -.border-radius-3 { +.border-radius-3-px { border-radius: 3px; } @@ -227,138 +227,138 @@ a:visited.background-color-hover-night-light:hover { padding-right: 0; } -.padding-4 { +.padding-4-px { padding: 4px; } -.padding-t-4 { +.padding-t-4-px { padding-top: 4px; } -.padding-y-4 { +.padding-y-4-px { padding-top: 4px; padding-bottom: 4px; } -.padding-x-4 { +.padding-x-4-px { padding-left: 4px; padding-right: 4px; } -.padding-x-8 { +.padding-x-8-px { padding-left: 8px; padding-right: 8px; } -.padding-y-6 { +.padding-y-6-px { padding-top: 6px; padding-bottom: 6px; } -.padding-8 { +.padding-8-px { padding: 8px; } -.padding-t-8 { +.padding-t-8-px { padding-top: 8px; } -.padding-b-8 { +.padding-b-8-px { padding-bottom: 8px; } -.padding-y-8 { +.padding-y-8-px { padding-top: 8px; padding-bottom: 8px; } -.padding-y-12 { +.padding-y-12-px { padding-top: 12px; padding-bottom: 12px; } -.padding-b-16 { +.padding-b-16-px { padding-bottom: 16px; } -.padding-t-16 { +.padding-t-16-px { padding-top: 16px; } -.padding-y-16 { +.padding-y-16-px { padding-top: 16px; padding-bottom: 16px; } -.padding-x-16 { +.padding-x-16-px { padding-left: 16px; padding-right: 16px; } -.padding-16 { +.padding-16-px { padding: 16px; } -.margin-l-4 { +.margin-l-4-px { margin-left: 4px; } -.margin-l-8 { +.margin-l-8-px { margin-left: 8px; } -.margin-l-16 { +.margin-l-16-px { margin-left: 16px; } -.margin-x-4 { +.margin-x-4-px { margin-left: 4px; margin-right: 4px; } -.margin-r-4 { +.margin-r-4-px { margin-right: 4px; } -.margin-r-8 { +.margin-r-8-px { margin-right: 8px; } -.margin-l-12 { +.margin-l-12-px { margin-left: 12px; } -.margin-l--196 { +.margin-l--196-px { margin-left: -196px; } -.margin-y-8 { +.margin-y-8-px { margin-top: 8px; margin-bottom: 8px; } -.margin-t-8 { +.margin-t-8-px { margin-top: 8px; } -.margin-b-8 { +.margin-b-8-px { margin-bottom: 8px; } -.margin-y-16 { +.margin-y-16-px { margin-top: 16px; margin-bottom: 16px; } -.margin-t-16 { +.margin-t-16-px { margin-top: 16px; } -.margin-b-16 { +.margin-b-16-px { margin-bottom: 16px; } -.margin-b-24 { +.margin-b-24-px { margin-bottom: 24px; } @@ -410,10 +410,6 @@ a:visited.background-color-hover-night-light:hover { width: 80%; } -.width-13px { - width: 13px; -} - .width-180-px { width: 180px; } diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..9982c21 --- /dev/null +++ b/public/index.php @@ -0,0 +1,9 @@ +render('default/home/index.html.twig'); + } +} \ No newline at end of file diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php new file mode 100644 index 0000000..7c45a43 --- /dev/null +++ b/src/Controller/PageController.php @@ -0,0 +1,67 @@ +render('default/page/submit.html.twig', [ + // @TODO + ]); + } + + #[Route( + '/{_locale}/page/stars', + name: 'page_stars' + )] + public function stars(): Response + { + // @TODO + } + + #[Route( + '/{_locale}/page/views', + name: 'page_views' + )] + public function views(): Response + { + // @TODO + } + + #[Route( + '/{_locale}/page/downloads', + name: 'page_downloads' + )] + public function downloads(): Response + { + // @TODO + } + + #[Route( + '/{_locale}/page/comments', + name: 'page_comments' + )] + public function comments(): Response + { + // @TODO + } + + #[Route( + '/{_locale}/page/editions', + name: 'page_editions' + )] + public function editions(): Response + { + // @TODO + } +} \ No newline at end of file diff --git a/src/Controller/ProfileController.php b/src/Controller/ProfileController.php new file mode 100644 index 0000000..c8a3b80 --- /dev/null +++ b/src/Controller/ProfileController.php @@ -0,0 +1,76 @@ +render( + 'default/profile/index.html.twig', + [ + 'user' => $user->init($request->getClientIp()) + ] + ); + } + + #[Route( + '/{_locale}/profile/setting', + name: 'profile_setting' + )] + public function setting(): Response + { + // @TODO + return $this->render( + 'default/profile/setting.html.twig' + ); + } + + public function module(string $route = ''): Response + { + return $this->render( + 'default/profile/module.html.twig', + [ + 'route' => $route, + 'stars' => 0, + 'views' => 0, + 'comments' => 0, + 'downloads' => 0, + 'editions' => 0, + 'identicon' => $this->_getIdenticon( + '@TODO', + 17, + [ + 'backgroundColor' => 'rgba(255, 255, 255, 0)', + ] + ) + ] + ); + } + + private function _getIdenticon( + mixed $id, + int $size, + array $style, + string $format = 'webp') : string + { + $identicon = new \Jdenticon\Identicon(); + + $identicon->setValue($id); + $identicon->setSize($size); + $identicon->setStyle($style); + + return $identicon->getImageDataUri($format); + } +} \ No newline at end of file diff --git a/src/Controller/SearchController.php b/src/Controller/SearchController.php new file mode 100644 index 0000000..7fea8a6 --- /dev/null +++ b/src/Controller/SearchController.php @@ -0,0 +1,31 @@ +query->get('query'); + + return $this->render('default/search/index.html.twig', [ + 'query' => $query + ]); + } + + public function module(string $query = ''): Response + { + return $this->render('default/search/module.html.twig', [ + 'query' => $query, + ]); + } +} \ No newline at end of file diff --git a/src/Entity/.gitignore b/src/Entity/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Entity/Page.php b/src/Entity/Page.php new file mode 100644 index 0000000..f7f25c8 --- /dev/null +++ b/src/Entity/Page.php @@ -0,0 +1,27 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..cf631a7 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,147 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getAddress(): ?string + { + return $this->address; + } + + public function setAddress(string $address): static + { + $this->address = $address; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function getUpdated(): ?int + { + return $this->updated; + } + + public function setUpdated(int $updated): static + { + $this->updated = $updated; + + return $this; + } + + public function getVisited(): ?int + { + return $this->visited; + } + + public function setVisited(int $visited): static + { + $this->visited = $visited; + + return $this; + } + + public function isPublic(): ?bool + { + return $this->public; + } + + public function setPublic(bool $public): static + { + $this->public = $public; + + return $this; + } + + public function isModerator(): ?bool + { + return $this->moderator; + } + + public function setModerator(bool $moderator): static + { + $this->moderator = $moderator; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } + + public function isStatus(): ?bool + { + return $this->status; + } + + public function setStatus(bool $status): static + { + $this->status = $status; + + return $this; + } +} diff --git a/src/Kernel.php b/src/Kernel.php new file mode 100644 index 0000000..779cd1f --- /dev/null +++ b/src/Kernel.php @@ -0,0 +1,11 @@ + + * + * @method Page|null find($id, $lockMode = null, $lockVersion = null) + * @method Page|null findOneBy(array $criteria, array $orderBy = null) + * @method Page[] findAll() + * @method Page[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PageRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Page::class); + } + +// /** +// * @return Page[] Returns an array of Page objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('p') +// ->andWhere('p.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('p.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Page +// { +// return $this->createQueryBuilder('p') +// ->andWhere('p.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..3767526 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,48 @@ + + * + * @method User|null find($id, $lockMode = null, $lockVersion = null) + * @method User|null findOneBy(array $criteria, array $orderBy = null) + * @method User[] findAll() + * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UserRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + +// /** +// * @return User[] Returns an array of User objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?User +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Service/User.php b/src/Service/User.php new file mode 100644 index 0000000..98890e0 --- /dev/null +++ b/src/Service/User.php @@ -0,0 +1,12 @@ +_database = $database; - $this->_validator = $validator; - $this->_website = $website; - $this->_session = $session; - } - - private function _initUser(string $address) - { - $error = []; - if (!$this->_validator->host($address, $error)) - { - $this->_response( - sprintf( - _('Error - %s'), - $this->_website->getName() - ), - _('406'), - $error, - 406 - ); - } - - try - { - $this->_database->beginTransaction(); - - $user = $this->_database->getUser( - $this->_database->initUserId( - $address, - $this->_website->getDefaultUserStatus(), - $this->_website->getDefaultUserApproved(), - time() - ) - ); - - $this->_database->commit(); - } - - catch (Exception $error) - { - $this->_database->rollback(); - - $this->_response( - sprintf( - _('Error - %s'), - $this->_website->getName() - ), - _('500'), - $error, - 500 - ); - } - - // Access denied - if (!$user->status) - { - $this->_response( - sprintf( - _('Error - %s'), - $this->_website->getName() - ), - _('403'), - _('Access denied'), - 403 - ); - } - } - - public function render() - { - $user = $this->_initUser( - $this->_session->getAddress() - ); - - $page = isset($_GET['page']) ? (int) $_GET['page'] : 1; - - $pages = []; - - require_once __DIR__ . '/module/pagination.php'; - - $appControllerModulePagination = new appControllerModulePagination(); - - require_once __DIR__ . '/module/head.php'; - - $appControllerModuleHead = new AppControllerModuleHead( - $this->_website->getUrl(), - $page > 1 ? - sprintf( - _('Page %s - BitTorrent Registry for Yggdrasil - %s'), - $page, - $this->_website->getName() - ) : - sprintf( - _('%s - BitTorrent Registry for Yggdrasil'), - $this->_website->getName() - ), - [ - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/common.css?%s', - CSS_VERSION - ), - ], - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/framework.css?%s', - CSS_VERSION - ), - ], - ] - ); - - require_once __DIR__ . '/module/profile.php'; - - $appControllerModuleProfile = new AppControllerModuleProfile( - $this->_database, - $this->_website, - $this->_session - ); - - require_once __DIR__ . '/module/header.php'; - - $appControllerModuleHeader = new AppControllerModuleHeader(); - - require_once __DIR__ . '/module/footer.php'; - - $appControllerModuleFooter = new AppControllerModuleFooter(); - - include __DIR__ . '/../view/theme/default/index.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/module/footer.php b/src/app/controller/module/footer.php deleted file mode 100644 index a512e0e..0000000 --- a/src/app/controller/module/footer.php +++ /dev/null @@ -1,13 +0,0 @@ -api->export; - - include __DIR__ . '../../../view/theme/default/module/footer.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/module/head.php b/src/app/controller/module/head.php deleted file mode 100644 index 781873b..0000000 --- a/src/app/controller/module/head.php +++ /dev/null @@ -1,54 +0,0 @@ -setBase($base); - $this->setTitle($title); - - foreach ($links as $link) - { - $this->addLink( - $link['rel'], - $link['type'], - $link['href'], - ); - } - } - - public function setBase(string $base) : void - { - $this->_base = $base; - } - - public function setTitle(string $title) : void - { - $this->_title = $title; - } - - public function addLink(string $rel, string $type, string $href) : void - { - $this->_links[] = (object) - [ - 'rel' => $rel, - 'type' => $type, - 'href' => $href, - ]; - } - - public function render() - { - $base = $this->_base; - - $links = $this->_links; - - $title = htmlentities($this->_title); - - include __DIR__ . '../../../view/theme/default/module/head.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/module/header.php b/src/app/controller/module/header.php deleted file mode 100644 index 293a268..0000000 --- a/src/app/controller/module/header.php +++ /dev/null @@ -1,19 +0,0 @@ -YGG', - Environment::config('website')->name - ); - - require_once __DIR__ . '/search.php'; - - $appControllerModuleSearch = new AppControllerModuleSearch(); - - include __DIR__ . '../../../view/theme/default/module/header.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/module/page.php b/src/app/controller/module/page.php deleted file mode 100644 index 036a6e7..0000000 --- a/src/app/controller/module/page.php +++ /dev/null @@ -1,9 +0,0 @@ - $limit) - { - parse_str($url, $query); - - $pagination->page = isset($query['total']) ? (int) $query['total'] : 1; - $pagination->pages = ceil($total / $limit); - - // Previous - if ($page > 1) - { - $query['page'] = $page - 1; - - $pagination->back = sprintf('%s', WEBSITE_URL, http_build_query($query)); - } - - else - { - $pagination->back = false; - } - - // Next - if ($page < ceil($total / $limit)) - { - $query['page'] = $page + 1; - - $pagination->next = sprintf('%s', WEBSITE_URL, http_build_query($query)); - } - - else - { - $pagination->next = false; - } - - // Render - } - } -} \ No newline at end of file diff --git a/src/app/controller/module/profile.php b/src/app/controller/module/profile.php deleted file mode 100644 index 0d82acc..0000000 --- a/src/app/controller/module/profile.php +++ /dev/null @@ -1,54 +0,0 @@ -_database = $database; - $this->_website = $website; - $this->_session = $session; - } - - public function render() - { - $route = isset($_GET['_route_']) ? (string) $_GET['_route_'] : ''; - - $user = $this->_database->getUser( - $this->_database->initUserId( - $this->_session->getAddress(), - $this->_website->getDefaultUserStatus(), - $this->_website->getDefaultUserApproved(), - time() - ) - ); - - $stars = $this->_database->findUserPageStarsDistinctTotalByValue($user->userId, true); - $views = $this->_database->findUserPageViewsDistinctTotal($user->userId); - $downloads = 0; // @TODO $this->_database->findUserPageDownloadsDistinctTotal($user->userId); - $comments = $this->_database->findUserPageCommentsDistinctTotal($user->userId); - $editions = 0; // @TODO $this->_database->findUserPageEditionsDistinctTotal($user->userId); - - $address = $user->address; - - $icon = new Jdenticon\Identicon(); - - $icon->setValue($user->address); - $icon->setSize(16); - $icon->setStyle( - [ - 'backgroundColor' => 'rgba(255, 255, 255, 0)', - ] - ); - - $identicon = $icon->getImageDataUri('webp'); - - include __DIR__ . '../../../view/theme/default/module/profile.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/module/search.php b/src/app/controller/module/search.php deleted file mode 100644 index ff1017f..0000000 --- a/src/app/controller/module/search.php +++ /dev/null @@ -1,24 +0,0 @@ - $value) - { - $locales[$key] = (object) - [ - 'key' => $key, - 'value' => $value[0], - 'active' => $key === $locale // false !== stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], $key) ? true : false, - ]; - } - - include __DIR__ . '../../../view/theme/default/module/search.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/page.php b/src/app/controller/page.php deleted file mode 100644 index a84c09c..0000000 --- a/src/app/controller/page.php +++ /dev/null @@ -1,428 +0,0 @@ -_database = $database; - $this->_validator = $validator; - $this->_locale = $locale; - $this->_website = $website; - $this->_session = $session; - $this->_request = $request; - } - - private function _response(string $title, string $h1, mixed $data, int $code = 200) - { - require_once __DIR__ . '/response.php'; - - if (is_array($data)) - { - $data = implode('
', $data); - } - - $appControllerResponse = new AppControllerResponse( - $title, - $h1, - $data, - $code - ); - - $appControllerResponse->render(); - - exit; - } - - private function _initUser(string $address) - { - $error = []; - if (!$this->_validator->host($address, $error)) - { - $this->_response( - sprintf( - _('Error - %s'), - $this->_website->getName() - ), - _('406'), - $error, - 406 - ); - } - - try - { - $this->_database->beginTransaction(); - - $user = $this->_database->getUser( - $this->_database->initUserId( - $address, - $this->_website->getDefaultUserStatus(), - $this->_website->getDefaultUserApproved(), - time() - ) - ); - - $this->_database->commit(); - } - - catch (Exception $error) - { - $this->_database->rollback(); - - $this->_response( - sprintf( - _('Error - %s'), - $this->_website->getName() - ), - _('500'), - $error, - 500 - ); - } - - // Access denied - if (!$user->status) - { - $this->_response( - sprintf( - _('Error - %s'), - $this->_website->getName() - ), - _('403'), - _('Access denied'), - 403 - ); - } - } - - private function _initLocale(string $value) - { - if (!$locale = $this->_database->findLocale($value)) - { - $locale = $this->_database->getLocale( - $this->_database->addLocale( - $value - ) - ); - } - - return $locale; - } - - private function _initPage(int $pageId = 0) - { - if (!$page = $this->_database->getPage($pageId)) - { - $page = $this->_database->getPage( - $this->_database->addPage( - time() - ) - ); - } - - return $page; - } - - private function _initText(string $value, string $mime = 'text/plain') - { - if (!$text = $this->_database->findText($mime, md5($value))) - { - $text = $this->_database->getText( - $this->_database->addText( - $mime, - md5($value), - $value, - time() - ) - ); - } - - return $text; - } - - private function _commitPageTitle(int $pageId, int $userId, int $localeId, string $text, string $mime = 'text/plain') - { - $textId = $this->_initText( - $text, - $mime - )->textId; - - if (!$this->_database->findPageTitleLatest($pageId, - $userId, - $localeId, - $textId)) - { - $this->_database->addPageTitle( - $pageId, - $userId, - $localeId, - $textId, - time() - ); - } - } - - public function renderFormSubmit() - { - // Init user - $user = $this->_initUser( - $this->_session->getAddress() - ); - - // Init page - if ($this->_request->get('pageId')) - { - $page = $this->_initPage( - (int) $this->_request->get('pageId') - ); - } - - else if ($this->_request->post('pageId')) - { - $page = $this->_initPage( - (int) $this->_request->post('pageId') - ); - } - - else - { - $page = $this->_initPage(); - } - - // Init locale - if ($this->_locale->codeExists($this->_request->get('locale'))) - { - $localeCode = (int) $this->_request->get('locale'); - } - - else if ($this->_locale->codeExists($this->_request->post('locale'))) - { - $localeCode = (int) $this->_request->post('locale'); - } - - else - { - $localeCode = $this->_website->getDefaultLocale(); - - if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) // @TODO environment - { - foreach ($this->_locale->getList() as $value) - { - if (false !== stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], $value->code)) - { - $localeCode = $value->code; - break; - } - } - } - } - - $locale = $this->_initLocale($localeCode); - - // Init form - $form = (object) - [ - 'pageId' => (object) - [ - 'error' => [], - 'type' => 'hidden', - 'attribute' => (object) - [ - 'value' => $page->pageId, - ] - ], - 'locale' => (object) - [ - 'error' => [], - 'type' => 'select', - 'options' => $this->_locale->getList(), - 'value' => $locale->value, - 'placeholder' => _('Page content language'), - ], - 'title' => (object) - [ - 'error' => [], - 'type' => 'text', - 'attribute' => (object) - [ - 'value' => null, - 'required' => $this->_validator->getPageTitleRequired(), - 'minlength' => $this->_validator->getPageTitleLengthMin(), - 'maxlength' => $this->_validator->getPageTitleLengthMax(), - 'placeholder' => sprintf( - _('Page subject (%s-%s chars)'), - number_format($this->_validator->getPageTitleLengthMin()), - number_format($this->_validator->getPageTitleLengthMax()) - ), - ] - ], - 'description' => (object) - [ - 'error' => [], - 'type' => 'textarea', - 'attribute' => (object) - [ - 'value' => null, - 'required' => $this->_validator->getPageDescriptionRequired(), - 'minlength' => $this->_validator->getPageDescriptionLengthMin(), - 'maxlength' => $this->_validator->getPageDescriptionLengthMax(), - 'placeholder' => sprintf( - _('Page description text (%s-%s chars)'), - number_format($this->_validator->getPageDescriptionLengthMin()), - number_format($this->_validator->getPageDescriptionLengthMax()) - ), - ] - ], - 'keywords' => (object) - [ - 'error' => [], - 'type' => 'textarea', - 'attribute' => (object) - [ - 'value' => null, - 'required' => $this->_validator->getPageKeywordsRequired(), - 'placeholder' => sprintf( - _('Page keywords (%s-%s total / %s-%s chars per item)'), - number_format($this->_validator->getPageKeywordsQuantityMin()), - number_format($this->_validator->getPageKeywordsQuantityMax()), - number_format($this->_validator->getPageKeywordLengthMin()), - number_format($this->_validator->getPageKeywordLengthMax()) - ), - ] - ], - 'sensitive' => (object) - [ - 'error' => [], - 'type' => 'checkbox', - 'attribute' => (object) - [ - 'value' => null, - 'placeholder' => _('Apply NSFW filters for this publication'), - ] - ] - ]; - - // Submit request - if ($this->_request->hasPost()) - { - /// Title - if ($title = $this->_request->post('title')) - { - $error = []; - - if (!$this->_validator->pageTitle($title, $error)) - { - $form->title->error[] = $error; - } - - else - { - $this->_commitPageTitle( - $page->pageId, - $user->userId, - $locale->localeId, - $title - ); - } - - $form->title->attribute->value = htmlentities($title); - } - - if (isset($_POST['description'])) - { - $error = []; - - if (!$this->_validator->pageDescription($_POST['description'], $error)) - { - $form->description->error[] = $error; - } - - $form->description->attribute->value = htmlentities($_POST['description']); - } - - if (isset($_POST['keywords'])) - { - $error = []; - - if (!$this->_validator->pageKeywords($_POST['keywords'], $error)) - { - $form->keywords->error[] = $error; - } - - $form->keywords->attribute->value = htmlentities($_POST['keywords']); - } - - if (isset($_POST['sensitive'])) - { - $form->sensitive->attribute->value = (bool) $_POST['sensitive']; - } - - // Request valid - if (empty($error)) - { - // @TODO redirect - } - } - - // Render template - require_once __DIR__ . '/module/head.php'; - - $appControllerModuleHead = new AppControllerModuleHead( - $this->_website->getUrl(), - sprintf( - _('Submit - %s'), - $this->_website->getName() - ), - [ - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/common.css?%s', - CSS_VERSION - ), - ], - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/framework.css?%s', - CSS_VERSION - ), - ], - ] - ); - - require_once __DIR__ . '/module/profile.php'; - - $appControllerModuleProfile = new AppControllerModuleProfile( - $this->_database, - $this->_website, - $this->_session - ); - - require_once __DIR__ . '/module/header.php'; - - $appControllerModuleHeader = new AppControllerModuleHeader(); - - require_once __DIR__ . '/module/footer.php'; - - $appControllerModuleFooter = new AppControllerModuleFooter(); - - include __DIR__ . '../../view/theme/default/page/form/submit.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/response.php b/src/app/controller/response.php deleted file mode 100644 index eec6b53..0000000 --- a/src/app/controller/response.php +++ /dev/null @@ -1,65 +0,0 @@ -_title = $title; - $this->_h1 = $h1; - $this->_text = $text; - $this->_code = $code; - } - - public function render() - { - header( - sprintf( - 'HTTP/1.0 %s Not Found', - $this->_code - ) - ); - - $h1 = $this->_h1; - $text = $this->_text; - - require_once __DIR__ . '/module/head.php'; - - $appControllerModuleHead = new AppControllerModuleHead( - Environment::config('website')->url, - $this->_title, - [ - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/common.css?%s', - CSS_VERSION - ), - ], - [ - 'rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => sprintf( - 'assets/theme/default/css/framework.css?%s', - CSS_VERSION - ), - ], - ] - ); - - require_once __DIR__ . '/module/header.php'; - - $appControllerModuleHeader = new AppControllerModuleHeader(); - - require_once __DIR__ . '/module/footer.php'; - - $appControllerModuleFooter = new AppControllerModuleFooter(); - - include __DIR__ . '../../view/theme/default/response.phtml'; - } -} \ No newline at end of file diff --git a/src/app/controller/user.php b/src/app/controller/user.php deleted file mode 100644 index f356a2b..0000000 --- a/src/app/controller/user.php +++ /dev/null @@ -1,117 +0,0 @@ -_database = $database; - $this->_validator = $validator; - $this->_website = $website; - } - - private function _response(string $title, string $h1, mixed $data, int $code = 200) - { - require_once __DIR__ . '/response.php'; - - if (is_array($data)) - { - $data = implode('
', $data); - } - - $appControllerResponse = new AppControllerResponse( - $title, - $h1, - $data, - $code - ); - - $appControllerResponse->render(); - - exit; - } - - public function getIdenticon(int $size) - { - $icon = new Jdenticon\Identicon(); - - $icon->setValue($this->_user->public ? $this->_user->address : $this->_user->userId); - $icon->setSize($size); - $icon->setStyle( - [ - 'backgroundColor' => 'rgba(255, 255, 255, 0)', - ] - ); - - return $icon->getImageDataUri('webp'); - } - - public function getUser() - { - return $this->_user; - } - - public function getPublic() - { - return $this->_user->public; - } - - public function getAddress() - { - return $this->_user->address; - } - - public function findUserPageStarsDistinctTotalByValue(bool $value) : int - { - return $this->_database->findUserPageStarsDistinctTotal( - $this->_user->userId, - $value - ); - } - - public function findUserPageViewsDistinctTotal() : int - { - return $this->_database->findUserPageViewsDistinctTotal( - $this->_user->userId - ); - } - - public function findUserPageDownloadsDistinctTotal() : int - { - return $this->_database->findUserPageDownloadsDistinctTotal( - $this->_user->userId - ); - } - - public function findUserPageCommentsDistinctTotal() : int - { - return $this->_database->findUserPageCommentsDistinctTotal( - $this->_user->userId - ); - } - - public function findUserPageEditionsDistinctTotal() : int - { - return $this->_database->findUserPageEditionsDistinctTotal( - $this->_user->userId - ); - } - - public function updateUserPublic(bool $public, int $time) : int - { - return $this->_database->updateUserPublic( - $this->_user->userId, - $public, - $time - ); - } -} \ No newline at end of file diff --git a/src/app/model/database.php b/src/app/model/database.php deleted file mode 100644 index 9bf5a8e..0000000 --- a/src/app/model/database.php +++ /dev/null @@ -1,1866 +0,0 @@ -_db = new PDO( - 'mysql:dbname=' . $config->name . ';host=' . $config->host . ';port=' . $config->port . ';charset=utf8', - $config->user, - $config->password, - [ - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' - ] - ); - - $this->_db->setAttribute( - PDO::ATTR_ERRMODE, - PDO::ERRMODE_EXCEPTION - ); - - $this->_db->setAttribute( - PDO::ATTR_DEFAULT_FETCH_MODE, - PDO::FETCH_OBJ - ); - - $this->_db->setAttribute( - PDO::ATTR_TIMEOUT, - 600 - ); - - $this->_debug = (object) - [ - 'query' => (object) - [ - 'select' => (object) - [ - 'total' => 0 - ], - 'insert' => (object) - [ - 'total' => 0 - ], - 'update' => (object) - [ - 'total' => 0 - ], - 'delete' => (object) - [ - 'total' => 0 - ], - ] - ]; - } - - // Tools - public function beginTransaction() : void - { - $this->_db->beginTransaction(); - } - - public function commit() : void - { - $this->_db->commit(); - } - - public function rollBack() : void - { - $this->_db->rollBack(); - } - - public function getDebug() : object - { - return $this->_debug; - } - - // Locale - public function addLocale(string $value) : int - { - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `locale` SET `value` = ?'); - - $query->execute([$value]); - - return $this->_db->lastInsertId(); - } - - public function getLocale(int $localeId) - { - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `locale` WHERE `localeId` = ?'); - - $query->execute([$localeId]); - - return $query->fetch(); - } - - public function findLocale(string $value) - { - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `locale` WHERE `value` = ?'); - - $query->execute([$value]); - - return $query->fetch(); - } - - // Text - public function addText(string $mime, string $hash, string $value) : int - { - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `text` SET `mime` = ?, `hash` = ?, `value` = ?'); - - $query->execute([$mime, $hash, $value]); - - return $this->_db->lastInsertId(); - } - - public function getText(int $textId) - { - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `text` WHERE `textId` = ?'); - - $query->execute([$textId]); - - return $query->fetch(); - } - - public function findText(string $mime, string $hash) - { - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `text` WHERE `mime` = ? AND `hash` = ?'); - - $query->execute([$mime, $hash]); - - return $query->fetch(); - } - - // Page - public function addPage(int $timeAdded) : int - { - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `page` SET `timeAdded` = ?'); - - $query->execute([$timeAdded]); - - return $this->_db->lastInsertId(); - } - - public function getPage(int $pageId) - { - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `page` WHERE `pageId` = ?'); - - $query->execute([$pageId]); - - return $query->fetch(); - } - - // Scheme - public function addScheme(string $value) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `scheme` SET `value` = ?'); - - $query->execute([$value]); - - return $this->_db->lastInsertId(); - } - - public function getScheme(int $schemeId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `scheme` WHERE `schemeId` = ?'); - - $query->execute([$schemeId]); - - return $query->fetch(); - } - - public function findScheme(string $value) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `scheme` WHERE `value` = ?'); - - $query->execute([$value]); - - return $query->fetch(); - } - - public function initSchemeId(string $value) : int { - - if ($result = $this->findScheme($value)) { - - return $result->schemeId; - } - - return $this->addScheme($value); - } - - // Host - public function addHost(string $value) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `host` SET `value` = ?'); - - $query->execute([$value]); - - return $this->_db->lastInsertId(); - } - - public function getHost(int $hostId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `host` WHERE `hostId` = ?'); - - $query->execute([$hostId]); - - return $query->fetch(); - } - - public function findHost(string $value) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `host` WHERE `value` = ?'); - - $query->execute([$value]); - - return $query->fetch(); - } - - public function initHostId(string $value) : int { - - if ($result = $this->findHost($value)) { - - return $result->hostId; - } - - return $this->addHost($value); - } - - // Port - public function addPort(mixed $value) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `port` SET `value` = ?'); - - $query->execute([$value]); - - return $this->_db->lastInsertId(); - } - - public function getPort(int $portId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `port` WHERE `portId` = ?'); - - $query->execute([$portId]); - - return $query->fetch(); - } - - public function findPort(mixed $value) { - - $this->_debug->query->select->total++; - - if ($value) { - - $query = $this->_db->prepare('SELECT * FROM `port` WHERE `value` = ?'); - - $query->execute([$value]); - - } else { - - $query = $this->_db->query('SELECT * FROM `port` WHERE `value` IS NULL'); - } - - return $query->fetch(); - } - - public function initPortId(mixed $value) : int { - - if ($result = $this->findPort($value)) { - - return $result->portId; - } - - return $this->addPort($value); - } - - // URI - public function addUri(mixed $value) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `uri` SET `value` = ?'); - - $query->execute([$value]); - - return $this->_db->lastInsertId(); - } - - public function getUri(int $uriId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `uri` WHERE `uriId` = ?'); - - $query->execute([$uriId]); - - return $query->fetch(); - } - - public function findUri(mixed $value) { - - $this->_debug->query->select->total++; - - if ($value) { - - $query = $this->_db->prepare('SELECT * FROM `uri` WHERE `value` = ?'); - - $query->execute([$value]); - - } else { - - $query = $this->_db->query('SELECT * FROM `uri` WHERE `value` IS NULL'); - } - - return $query->fetch(); - } - - public function initUriId(mixed $value) : int { - - if ($result = $this->findUri($value)) { - - return $result->uriId; - } - - return $this->addUri($value); - } - - // Info Hash - public function addInfoHash(mixed $value, int $version) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `infoHash` SET `value` = ?, `version` = ?'); - - $query->execute([$value, $version]); - - return $this->_db->lastInsertId(); - } - - public function getInfoHash(int $infoHashId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `infoHash` WHERE `infoHashId` = ?'); - - $query->execute([$infoHashId]); - - return $query->fetch(); - } - - public function findInfoHash(string $value, int $version) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `infoHash` WHERE `value` = ? AND `version` = ?'); - - $query->execute([$value, $version]); - - return $query->fetch(); - } - - public function initInfoHashId(mixed $value, int $version) : int { - - if ($result = $this->findInfoHash($value, $version)) { - - return $result->infoHashId; - } - - return $this->addInfoHash($value, $version); - } - - // Address Tracker - public function addAddressTracker(int $schemeId, int $hostId, mixed $portId, mixed $uriId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `addressTracker` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); - - $query->execute([$schemeId, $hostId, $portId, $uriId]); - - return $this->_db->lastInsertId(); - } - - public function getAddressTracker(int $addressTrackerId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `addressTrackerId` = ?'); - - $query->execute([$addressTrackerId]); - - return $query->fetch(); - } - - public function findAddressTracker(int $schemeId, int $hostId, mixed $portId, mixed $uriId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `addressTracker` WHERE `schemeId` = ' . (int) $schemeId . ' - AND `hostId` = ' . (int) $hostId . ' - AND `portId` ' . ($portId ? ' = ' . (int) $portId : ' IS NULL ') . ' - AND `uriId` ' . ($uriId ? ' = ' . (int) $uriId : ' IS NULL ')); - - return $query->fetch(); - } - - public function initAddressTrackerId(int $schemeId, int $hostId, mixed $portId, mixed $uriId) : int { - - if ($result = $this->findAddressTracker($schemeId, $hostId, $portId, $uriId)) { - - return $result->addressTrackerId; - } - - return $this->addAddressTracker($schemeId, $hostId, $portId, $uriId); - } - - // Acceptable Source - public function addAcceptableSource(int $schemeId, int $hostId, mixed $portId, mixed $uriId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `acceptableSource` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); - - $query->execute([$schemeId, $hostId, $portId, $uriId]); - - return $this->_db->lastInsertId(); - } - - public function getAcceptableSource(int $acceptableSourceId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `acceptableSourceId` = ?'); - - $query->execute([$acceptableSourceId]); - - return $query->fetch(); - } - - public function findAcceptableSource(int $schemeId, int $hostId, mixed $portId, mixed $uriId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `acceptableSource` WHERE `schemeId` = ' . (int) $schemeId . ' - AND `hostId` = ' . (int) $hostId . ' - AND `portId` ' . ($portId ? ' = ' . (int) $portId : ' IS NULL ') . ' - AND `uriId` ' . ($uriId ? ' = ' . (int) $uriId : ' IS NULL ')); - - return $query->fetch(); - } - - public function initAcceptableSourceId(int $schemeId, int $hostId, mixed $portId, mixed $uriId) : int { - - if ($result = $this->findAcceptableSource($schemeId, $hostId, $portId, $uriId)) { - - return $result->acceptableSourceId; - } - - return $this->addAcceptableSource($schemeId, $hostId, $portId, $uriId); - } - - // eXact Source - public function addExactSource(int $schemeId, int $hostId, mixed $portId, mixed $uriId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `eXactSource` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); - - $query->execute([$schemeId, $hostId, $portId, $uriId]); - - return $this->_db->lastInsertId(); - } - - public function getExactSource(int $eXactSourceId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `eXactSourceId` = ?'); - - $query->execute([$eXactSourceId]); - - return $query->fetch(); - } - - public function findExactSource(int $schemeId, int $hostId, mixed $portId, mixed $uriId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `eXactSource` WHERE `schemeId` = ' . (int) $schemeId . ' - AND `hostId` = ' . (int) $hostId . ' - AND `portId` ' . ($portId ? ' = ' . (int) $portId : ' IS NULL ') . ' - AND `uriId` ' . ($uriId ? ' = ' . (int) $uriId : ' IS NULL ')); - - return $query->fetch(); - } - - public function initExactSourceId(int $schemeId, int $hostId, mixed $portId, mixed $uriId) : int { - - if ($result = $this->findExactSource($schemeId, $hostId, $portId, $uriId)) { - - return $result->eXactSourceId; - } - - return $this->addExactSource($schemeId, $hostId, $portId, $uriId); - } - - // Keyword Topic - public function addKeywordTopic(string $value) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `keywordTopic` SET `value` = ?'); - - $query->execute([$value]); - - return $this->_db->lastInsertId(); - } - - public function findKeywordTopic(string $value) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `keywordTopic` WHERE `value` = ?'); - - $query->execute([$value]); - - return $query->fetch(); - } - - public function getKeywordTopic(int $keywordTopicId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `keywordTopic` WHERE `keywordTopicId` = ?'); - - $query->execute([$keywordTopicId]); - - return $query->fetch(); - } - - public function initKeywordTopicId(string $value) : int { - - if ($result = $this->findKeywordTopic($value)) { - - return $result->keywordTopicId; - } - - return $this->addKeywordTopic($value); - } - - // User - public function addUser(string $address, bool $status, bool $approved, $timeAdded) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `user` SET `address` = ?, `status` = ?, `approved` = ?, `timeAdded` = ?'); - - $query->execute([$address, (int) $status, (int) $approved, $timeAdded]); - - return $this->_db->lastInsertId(); - } - - public function getUser(int $userId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `user` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch(); - } - - public function getUsers() { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `user`'); - - return $query->fetchAll(); - } - - public function getUsersTotal() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `user`'); - - $query->execute(); - - return $query->fetch()->result; - } - - public function getUsersTotalByPublic(mixed $public) : int { - - $this->_debug->query->select->total++; - - if (is_null($public)) - { - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `user` WHERE `public` IS NULL'); - $query->execute(); - } - else - { - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `user` WHERE `public` = ?'); - $query->execute([(int) $public]); - } - - return $query->fetch()->result; - } - - public function findUserByAddress(string $address) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `user` WHERE `address` = ?'); - - $query->execute([$address]); - - return $query->fetch(); - } - - public function initUserId(string $address, bool $status, bool $approved, int $timeAdded) : int { - - if ($result = $this->findUserByAddress($address)) { - - return $result->userId; - } - - return $this->addUser($address, $status, $approved, $timeAdded); - } - - public function updateUserApproved(int $userId, mixed $approved, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `user` SET `approved` = ?, `timeUpdated` = ? WHERE `userId` = ?'); - - $query->execute([(int) $approved, $timeUpdated, $userId]); - - return $query->rowCount(); - } - - public function updateUserPublic(int $userId, bool $public, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `user` SET `public` = ?, `timeUpdated` = ? WHERE `userId` = ?'); - - $query->execute([(int) $public, $timeUpdated, $userId]); - - return $query->rowCount(); - } - - public function updateUserTimeAdded(int $userId, int $timeAdded) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `user` SET `timeAdded` = ? WHERE `userId` = ?'); - - $query->execute([$timeAdded, $userId]); - - return $query->rowCount(); - } - - public function updateUserTimeUpdated(int $userId, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `user` SET `timeUpdated` = ? WHERE `userId` = ?'); - - $query->execute([$timeUpdated, $userId]); - - return $query->rowCount(); - } - - public function findUserPageStarsDistinctTotalByValue(int $userId, bool $value) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `pageStar` WHERE `userId` = ? AND `value` = ?'); - - $query->execute([$userId, (int) $value]); - - return $query->fetch()->result; - } - - public function findUserPageViewsDistinctTotal(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `pageView` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function findUserPageCommentsDistinctTotal(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `pageComment` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function findUserPageEditionsDistinctTotal(int $userId) : int { - - return 0; - - /* @TODO - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `pageId`) AS `result` FROM `userPageEdition` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - */ - } - - // Magnet - public function addMagnet(int $userId, - int $xl, - string $dn, - string $linkSource, - bool $public, - bool $comments, - bool $sensitive, - bool $approved, - int $timeAdded, - mixed $timeUpdated = null) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnet` SET `userId` = ?, - `xl` = ?, - `dn` = ?, - `linkSource` = ?, - `public` = ?, - `comments` = ?, - `sensitive` = ?, - `approved` = ?, - `timeAdded` = ?, - `timeUpdated` = ?'); - - $query->execute( - [ - $userId, - $xl, - $dn, - $linkSource, - $public ? 1 : 0, - $comments ? 1 : 0, - $sensitive ? 1 : 0, - $approved ? 1 : 0, - $timeAdded, - $timeUpdated - ] - ); - - return $this->_db->lastInsertId(); - } - - public function getMagnet(int $magnetId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetch(); - } - - public function getMagnets() { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnet`'); - - $query->execute(); - - return $query->fetchAll(); - } - - public function getMagnetsTotal() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnet`'); - - $query->execute(); - - return $query->fetch()->result; - } - - public function findMagnet(int $userId, int $timeAdded) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `userId` = ? AND `timeAdded` = ?'); - - $query->execute([$userId, $timeAdded]); - - return $query->fetch(); - } - - public function findMagnetsByUserId(int $userId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetchAll(); - } - - public function findMagnetsTotalByUserId(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnet` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function getMagnetsTotalByUsersPublic(bool $public) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnet` - JOIN `user` ON (`user`.`userId` = `magnet`.`userId`) - WHERE `user`.`public` = ?'); - - $query->execute([(int) $public]); - - return $query->fetch()->result; - } - - public function updateMagnetXl(int $magnetId, int $xl, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `xl` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([$xl, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetDn(int $magnetId, string $dn, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `dn` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([$dn, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetTitle(int $magnetId, string $title, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `title` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([$title, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetPreview(int $magnetId, string $preview, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `preview` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([$preview, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetDescription(int $magnetId, string $description, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `description` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([$description, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetPublic(int $magnetId, bool $public, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `public` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([(int) $public, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetComments(int $magnetId, bool $comments, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `comments` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([(int) $comments, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetSensitive(int $magnetId, bool $sensitive, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `sensitive` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([(int) $sensitive, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetApproved(int $magnetId, bool $approved, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `approved` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([(int) $approved, $timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetTimeUpdated(int $magnetId, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `timeUpdated` = ? WHERE `magnetId` = ?'); - - $query->execute([$timeUpdated, $magnetId]); - - return $query->rowCount(); - } - - public function updateMagnetTimeAdded(int $magnetId, int $timeAdded) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnet` SET `timeAdded` = ? WHERE `magnetId` = ?'); - - $query->execute([$timeAdded, $magnetId]); - - return $query->rowCount(); - } - - // Magnet to Info Hash - public function addMagnetToInfoHash(int $magnetId, int $infoHashId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetToInfoHash` SET `magnetId` = ?, `infoHashId` = ?'); - - $query->execute([$magnetId, $infoHashId]); - - return $this->_db->lastInsertId(); - } - - public function findMagnetToInfoHashByMagnetId(int $magnetId) - { - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToInfoHash` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetchAll(); - } - - // Magnet to AddressTracker - public function addMagnetToAddressTracker(int $magnetId, int $addressTrackerId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetToAddressTracker` SET `magnetId` = ?, `addressTrackerId` = ?'); - - $query->execute([$magnetId, $addressTrackerId]); - - return $this->_db->lastInsertId(); - } - - public function updateMagnetToAddressTrackerSeeders(int $magnetToAddressTrackerId, int $seeders, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetToAddressTracker` SET `seeders` = ?, `timeUpdated` = ? WHERE `magnetToAddressTrackerId` = ?'); - - $query->execute([$seeders, $timeUpdated, $magnetToAddressTrackerId]); - - return $query->rowCount(); - } - - public function updateMagnetToAddressTrackerCompleted(int $magnetToAddressTrackerId, int $completed, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetToAddressTracker` SET `completed` = ?, `timeUpdated` = ? WHERE `magnetToAddressTrackerId` = ?'); - - $query->execute([$completed, $timeUpdated, $magnetToAddressTrackerId]); - - return $query->rowCount(); - } - - public function updateMagnetToAddressTrackerLeechers(int $magnetToAddressTrackerId, int $leechers, int $timeUpdated) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetToAddressTracker` SET `leechers` = ?, `timeUpdated` = ? WHERE `magnetToAddressTrackerId` = ?'); - - $query->execute([$leechers, $timeUpdated, $magnetToAddressTrackerId]); - - return $query->rowCount(); - } - - public function updateMagnetToAddressTrackerTimeOffline(int $magnetToAddressTrackerId, mixed $timeOffline) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetToAddressTracker` SET `timeOffline` = ? WHERE `magnetToAddressTrackerId` = ?'); - - $query->execute([$timeOffline, $magnetToAddressTrackerId]); - - return $query->rowCount(); - } - - public function deleteMagnetToAddressTrackerByMagnetId(int $magnetId) : int { - - $this->_debug->query->delete->total++; - - $query = $this->_db->prepare('DELETE FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->rowCount(); - } - - public function findMagnetToAddressTracker(int $magnetId, int $addressTrackerId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` WHERE `magnetId` = ? AND `addressTrackerId` = ?'); - - $query->execute([$magnetId, $addressTrackerId]); - - return $query->fetch(); - } - - public function findAddressTrackerByMagnetId(int $magnetId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetchAll(); - } - - public function getMagnetToAddressTrackerScrapeQueue(int $limit) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` - - WHERE `timeOffline` IS NULL - - ORDER BY `timeUpdated` ASC, RAND() - - LIMIT ' . (int) $limit); - - $query->execute(); - - return $query->fetchAll(); - } - - public function resetMagnetToAddressTrackerTimeOfflineByTimeout(int $timeOffline) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetToAddressTracker` SET `timeOffline` = NULL WHERE `timeOffline` < ?'); - - $query->execute( - [ - time() - $timeOffline - ] - ); - - return $query->rowCount(); - } - - public function initMagnetToAddressTrackerId(int $magnetId, int $addressTrackerId) : int { - - if ($result = $this->findMagnetToAddressTracker($magnetId, $addressTrackerId)) { - - return $result->magnetToAddressTrackerId; - } - - return $this->addMagnetToAddressTracker($magnetId, $addressTrackerId); - } - - public function getMagnetToAddressTrackerSeedersSumByMagnetId(int $magnetId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT SUM(`seeders`) AS `result` FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return (int) $query->fetch()->result; - } - - public function getMagnetToAddressTrackerCompletedSumByMagnetId(int $magnetId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT SUM(`completed`) AS `result` FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return (int) $query->fetch()->result; - } - - public function getMagnetToAddressTrackerLeechersSumByMagnetId(int $magnetId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT SUM(`leechers`) AS `result` FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return (int) $query->fetch()->result; - } - - public function getMagnetToAcceptableSourceTotalByMagnetId(int $magnetId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return (int) $query->fetch()->result; - } - - public function getMagnetToAddressTrackerSeedersSum() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT SUM(`seeders`) AS `result` FROM `magnetToAddressTracker`'); - - $query->execute(); - - return (int) $query->fetch()->result; - } - - public function getMagnetToAddressTrackerCompletedSum() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT SUM(`completed`) AS `result` FROM `magnetToAddressTracker`'); - - $query->execute(); - - return (int) $query->fetch()->result; - } - - public function getMagnetToAddressTrackerLeechersSum() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT SUM(`leechers`) AS `result` FROM `magnetToAddressTracker`'); - - $query->execute(); - - return (int) $query->fetch()->result; - } - - // Magnet to AcceptableSource - public function addMagnetToAcceptableSource(int $magnetId, int $acceptableSourceId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetToAcceptableSource` SET `magnetId` = ?, `acceptableSourceId` = ?'); - - $query->execute([$magnetId, $acceptableSourceId]); - - return $this->_db->lastInsertId(); - } - - public function deleteMagnetToAcceptableSourceByMagnetId(int $magnetId) : int { - - $this->_debug->query->delete->total++; - - $query = $this->_db->prepare('DELETE FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->rowCount(); - } - - public function findMagnetToAcceptableSource(int $magnetId, int $acceptableSourceId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToAcceptableSource` WHERE `magnetId` = ? AND `acceptableSourceId` = ?'); - - $query->execute([$magnetId, $acceptableSourceId]); - - return $query->fetch(); - } - - public function findAcceptableSourceByMagnetId(int $magnetId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetchAll(); - } - - public function initMagnetToAcceptableSourceId(int $magnetId, int $acceptableSourceId) : int { - - if ($result = $this->findMagnetToAcceptableSource($magnetId, $acceptableSourceId)) { - - return $result->magnetToAcceptableSourceId; - } - - return $this->addMagnetToAcceptableSource($magnetId, $acceptableSourceId); - } - - // Magnet to eXactSource - public function addMagnetToExactSource(int $magnetId, int $eXactSourceId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetToExactSource` SET `magnetId` = ?, `eXactSourceId` = ?'); - - $query->execute([$magnetId, $eXactSourceId]); - - return $this->_db->lastInsertId(); - } - - public function deleteMagnetToExactSourceByMagnetId(int $magnetId) : int { - - $this->_debug->query->delete->total++; - - $query = $this->_db->prepare('DELETE FROM `magnetToExactSource` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $this->_db->lastInsertId(); - } - - public function findMagnetToExactSource(int $magnetId, int $eXactSourceId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToExactSource` WHERE `magnetId` = ? AND `eXactSourceId` = ?'); - - $query->execute([$magnetId, $eXactSourceId]); - - return $query->fetch(); - } - - public function findExactSourceByMagnetId(int $magnetId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToExactSource` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetchAll(); - } - - public function initMagnetToExactSourceId(int $magnetId, int $eXactSourceId) : int { - - if ($result = $this->findMagnetToEXactSource($magnetId, $eXactSourceId)) { - - return $result->magnetToExactSourceId; - } - - return $this->addMagnetToEXactSource($magnetId, $eXactSourceId); - } - - // Magnet to KeywordTopic - public function addMagnetToKeywordTopic(int $magnetId, int $keywordTopicId) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetToKeywordTopic` SET `magnetId` = ?, `keywordTopicId` = ?'); - - $query->execute([$magnetId, $keywordTopicId]); - - return $this->_db->lastInsertId(); - } - - public function deleteMagnetToKeywordTopicByMagnetId(int $magnetId) : int { - - $this->_debug->query->delete->total++; - - $query = $this->_db->prepare('DELETE FROM `magnetToKeywordTopic` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->rowCount(); - } - - public function findMagnetToKeywordTopic(int $magnetId, int $keywordTopicId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToKeywordTopic` WHERE `magnetId` = ? AND `keywordTopicId` = ?'); - - $query->execute([$magnetId, $keywordTopicId]); - - return $query->fetch(); - } - - public function findKeywordTopicByMagnetId(int $magnetId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetToKeywordTopic` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetchAll(); - } - - public function initMagnetToKeywordTopicId(int $magnetId, int $keywordTopicId) : int { - - if ($result = $this->findMagnetToKeywordTopic($magnetId, $keywordTopicId)) { - - return $result->magnetToKeywordTopicId; - } - - return $this->addMagnetToKeywordTopic($magnetId, $keywordTopicId); - } - - // Magnet lock - public function addMagnetLock(int $magnetId, int $userId, int $timeAdded) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetLock` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $this->_db->lastInsertId(); - } - - public function flushMagnetLock(int $magnetId) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('DELETE FROM `magnetLock` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->rowCount(); - } - - public function findLastMagnetLock(int $magnetId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetLock` WHERE `magnetId` = ? ORDER BY `magnetLockId` DESC LIMIT 1'); - - $query->execute([$magnetId]); - - return $query->fetch(); - } - - // Magnet comment - public function addMagnetComment( int $magnetId, - int $userId, - mixed $magnetCommentIdParent, - string $value, - bool $approved, - bool $public, - int $timeAdded) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetComment` SET `magnetId` = ?, - `userId` = ?, - `magnetCommentIdParent` = ?, - `value` = ?, - `approved` = ?, - `public` = ?, - `timeAdded` = ?'); - - $query->execute( - [ - $magnetId, - $userId, - $magnetCommentIdParent, - $value, - $approved, - $public, - $timeAdded - ] - ); - - return $this->_db->lastInsertId(); - } - - public function updateMagnetCommentPublic(int $magnetCommentId, mixed $public) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetComment` SET `public` = ? WHERE `magnetCommentId` = ?'); - - $query->execute([(int) $public, $magnetCommentId]); - - return $query->rowCount(); - } - - public function updateMagnetCommentApproved(int $magnetCommentId, mixed $approved) : int { - - $this->_debug->query->update->total++; - - $query = $this->_db->prepare('UPDATE `magnetComment` SET `approved` = ? WHERE `magnetCommentId` = ?'); - - $query->execute([(int) $approved, $magnetCommentId]); - - return $query->rowCount(); - } - - public function getMagnetCommentsTotal() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT COUNT(*) AS `result` FROM `magnetComment`'); - - return $query->fetch()->result; - } - - public function getMagnetComments() { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `magnetComment`'); - - return $query->fetchAll(); - } - - public function findMagnetCommentsTotalByMagnetId(int $magnetId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `userId`) AS `result` FROM `magnetComment` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetch()->result; - } - - public function findMagnetCommentsTotalByUserId(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `magnetId`) AS `result` FROM `magnetComment` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function findMagnetCommentsTotal(int $magnetId, int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetComment` WHERE `magnetId` = ? AND `userId` = ?'); - - $query->execute([$magnetId, $userId]); - - return $query->fetch()->result; - } - - public function findMagnetCommentsTotalByUsersPublic(bool $public) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetComment` - JOIN `user` ON (`user`.`userId` = `magnetComment`.`userId`) - WHERE `user`.`public` = ?'); - - $query->execute([(int) $public]); - - return $query->fetch()->result; - } - - public function findMagnetComments(int $magnetId, mixed $magnetCommentIdParent = null) { - - $this->_debug->query->select->total++; - - if ($magnetCommentIdParent) - { - $query = $this->_db->prepare('SELECT * FROM `magnetComment` WHERE `magnetId` = ? AND `magnetCommentIdParent` = ?'); - - $query->execute([$magnetId, $magnetCommentIdParent]); - } - else - { - $query = $this->_db->prepare('SELECT * FROM `magnetComment` WHERE `magnetId` = ? AND `magnetCommentIdParent` IS NULL'); - - $query->execute([$magnetId]); - } - - return $query->fetchAll(); - } - - public function findMagnetComment(int $magnetId, int $userId, int $timeAdded) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetComment` WHERE `magnetId` = ? AND `userId` = ? AND `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $query->fetch(); - } - - public function getMagnetComment(int $magnetCommentId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetComment` WHERE `magnetCommentId` = ?'); - - $query->execute([$magnetCommentId]); - - return $query->fetch(); - } - - // Magnet star - public function addMagnetStar(int $magnetId, int $userId, bool $value, int $timeAdded) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetStar` SET `magnetId` = ?, `userId` = ?, `value` = ?, `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, (int) $value, $timeAdded]); - - return $this->_db->lastInsertId(); - } - - public function getMagnetStar(int $magnetStarId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetStar` WHERE `magnetStarId` = ?'); - - $query->execute([$magnetStarId]); - - return $query->fetch(); - } - - public function getMagnetStars() { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `magnetStar`'); - - return $query->fetchAll(); - } - - public function getMagnetStarsTotal() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT COUNT(*) AS `result` FROM `magnetStar`'); - - return $query->fetch()->result; - } - - public function findMagnetStarsTotalByMagnetId(int $magnetId, bool $value) : int { - - $this->_debug->query->select->total++; - - $total = 0; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `userId`) AS `result` FROM `magnetStar` WHERE `magnetId` = ? AND `value` = ?'); - - $query->execute([$magnetId, (int) $value]); - - return $query->fetch()->result; - } - - public function findMagnetStarsTotalByUserId(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `magnetId`) AS `result` FROM `magnetStar` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function findLastMagnetStarValue(int $magnetId, int $userId) : bool { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetStar` WHERE `magnetId` = ? AND `userId` = ? ORDER BY `magnetStarId` DESC'); - - $query->execute([$magnetId, $userId]); - - return $query->rowCount() ? (bool) $query->fetch()->value : false; - } - - public function findMagnetStarsTotalByUsersPublic(bool $public) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetStar` - JOIN `user` ON (`user`.`userId` = `magnetStar`.`userId`) - WHERE `user`.`public` = ?'); - - $query->execute([(int) $public]); - - return $query->fetch()->result; - } - - public function findMagnetStar(int $magnetId, int $userId, int $timeAdded) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetStar` WHERE `magnetId` = ? AND `userId` = ? AND `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $query->fetch(); - } - - // Magnet download - public function addMagnetDownload(int $magnetId, int $userId, int $timeAdded) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetDownload` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $this->_db->lastInsertId(); - } - - public function getMagnetDownload(int $magnetDownloadId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetDownload` WHERE `magnetDownloadId` = ?'); - - $query->execute([$magnetDownloadId]); - - return $query->fetch(); - } - - public function getMagnetDownloads() { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `magnetDownload`'); - - return $query->fetchAll(); - } - - public function getMagnetDownloadsTotal() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT COUNT(*) AS `result` FROM `magnetDownload`'); - - return $query->fetch()->result; - } - - public function findMagnetDownloadsTotal(int $magnetId, int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetDownload` WHERE `magnetId` = ? AND `userId` = ?'); - - $query->execute([$magnetId, $userId]); - - return $query->fetch()->result; - } - - public function findMagnetDownload(int $magnetId, int $userId, int $timeAdded) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetDownload` WHERE `magnetId` = ? AND `userId` = ? AND `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $query->fetch(); - } - - public function findMagnetDownloadsTotalByMagnetId(int $magnetId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `userId`) AS `result` FROM `magnetDownload` WHERE `magnetId` = ?'); - - $query->execute([$magnetId]); - - return $query->fetch()->result; - } - - public function findMagnetDownloadsTotalByUserId(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(DISTINCT `magnetId`) AS `result` FROM `magnetDownload` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function findMagnetDownloadsTotalByUsersPublic(bool $public) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetDownload` - JOIN `user` ON (`user`.`userId` = `magnetDownload`.`userId`) - WHERE `user`.`public` = ?'); - - $query->execute([(int) $public]); - - return $query->fetch()->result; - } - - // Magnet view - public function addMagnetView(int $magnetId, int $userId, int $timeAdded) : int { - - $this->_debug->query->insert->total++; - - $query = $this->_db->prepare('INSERT INTO `magnetView` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $this->_db->lastInsertId(); - } - - public function getMagnetViews() { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT * FROM `magnetView`'); - - return $query->fetchAll(); - } - - public function getMagnetView(int $magnetViewId) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetView` WHERE `magnetViewId` = ?'); - - $query->execute([$magnetViewId]); - - return $query->fetch(); - } - - public function getMagnetViewsTotal() : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->query('SELECT COUNT(*) AS `result` FROM `magnetView`'); - - return $query->fetch()->result; - } - - public function findMagnetViewsTotalByUserId(int $userId) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetView` WHERE `userId` = ?'); - - $query->execute([$userId]); - - return $query->fetch()->result; - } - - public function findMagnetViewsTotalByUsersPublic(bool $public) : int { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetView` - JOIN `user` ON (`user`.`userId` = `magnetView`.`userId`) - WHERE `user`.`public` = ?'); - - $query->execute([(int) $public]); - - return $query->fetch()->result; - } - - public function findMagnetView(int $magnetId, int $userId, int $timeAdded) { - - $this->_debug->query->select->total++; - - $query = $this->_db->prepare('SELECT * FROM `magnetView` WHERE `magnetId` = ? AND `userId` = ? AND `timeAdded` = ?'); - - $query->execute([$magnetId, $userId, $timeAdded]); - - return $query->fetch(); - } -} \ No newline at end of file diff --git a/src/app/model/locale.php b/src/app/model/locale.php deleted file mode 100644 index 91272b1..0000000 --- a/src/app/model/locale.php +++ /dev/null @@ -1,37 +0,0 @@ - $value) - { - $this->_locales[] = (object) - [ - 'code' => $code, - 'value' => $value[0], - 'active' => false, - ]; - } - } - - public function getList() : object - { - return (object) $this->_locales; - } - - public function codeExists(string $code) : bool - { - foreach ($this->_locales as $locale) - { - if ($locale->code === $code) - { - return true; - } - } - - return false; - } -} diff --git a/src/app/model/request.php b/src/app/model/request.php deleted file mode 100644 index e6697fa..0000000 --- a/src/app/model/request.php +++ /dev/null @@ -1,104 +0,0 @@ -_get = $get; - $this->_post = $post; - $this->_files = $files; - $this->_server = $server; - } - - public function get(string $key, mixed $value = null) : mixed - { - if ($value) - { - $this->_get[$key] = $value; - } - - if (isset($this->_get[$key])) - { - return $this->_get[$key]; - } - - else - { - return false; - } - } - - public function post(string $key, mixed $value = null) : mixed - { - if ($value) - { - $this->_get[$key] = $value; - } - - if (isset($this->_post[$key])) - { - return $this->_post[$key]; - } - - else - { - return false; - } - } - - public function files(string $key, mixed $value = null) : mixed - { - if ($value) - { - $this->_get[$key] = $value; - } - - if (isset($this->_files[$key])) - { - return $this->_files[$key]; - } - - else - { - return false; - } - } - - public function server(string $key, mixed $value = null) : mixed - { - if ($value) - { - $this->_get[$key] = $value; - } - - if (isset($this->_get[$key])) - { - return $this->_get[$key]; - } - - else - { - return false; - } - } - - public function hasPost() : bool - { - return !empty($this->_post); - } - - public function hasGet() : bool - { - return !empty($this->_get); - } - - public function hasFiles() : bool - { - return !empty($this->_files); - } -} diff --git a/src/app/model/sphinx.php b/src/app/model/sphinx.php deleted file mode 100644 index db2f8be..0000000 --- a/src/app/model/sphinx.php +++ /dev/null @@ -1,111 +0,0 @@ -_sphinx = new PDO('mysql:host=' . $host . ';port=' . $port . ';charset=utf8', false, false, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); - $this->_sphinx->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->_sphinx->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); - } - - public function searchMagnetsTotal(string $keyword, string $mode = 'default', array $stopWords = []) : int - { - $query = $this->_sphinx->prepare('SELECT COUNT(*) AS `total` FROM `magnet` WHERE MATCH(?)'); - - $query->execute( - [ - self::_match($keyword, $mode, $stopWords) - ] - ); - - return $query->fetch()->total; - } - - public function searchMagnets(string $keyword, int $start, int $limit, int $maxMatches, string $mode = 'default', array $stopWords = []) - { - $query = $this->_sphinx->prepare("SELECT * - - FROM `magnet` - - WHERE MATCH(?) - - ORDER BY `magnetId` DESC, WEIGHT() DESC - - LIMIT " . (int) ($start >= $maxMatches ? ($maxMatches > 0 ? $maxMatches - 1 : 0) : $start) . "," . (int) $limit . " - - OPTION `max_matches`=" . (int) ($maxMatches >= 1 ? $maxMatches : 1)); - - $query->execute( - [ - self::_match($keyword, $mode, $stopWords) - ] - ); - - return $query->fetchAll(); - } - - private static function _match(string $keyword, string $mode = 'default', array $stopWords = []) : string - { - $keyword = trim($keyword); - - if (empty($keyword)) - { - return $keyword; - } - - $keyword = str_replace(['"'], ' ', $keyword); - $keyword = preg_replace('/[\W]/ui', ' ', $keyword); - $keyword = preg_replace('/[\s]+/ui', ' ', $keyword); - $keyword = trim($keyword); - - switch ($mode) - { - case 'similar': - - $result = []; - - $keyword = preg_replace('/[\d]/ui', ' ', $keyword); - $keyword = preg_replace('/[\s]+/ui', ' ', $keyword); - $keyword = trim($keyword); - - foreach ((array) explode(' ', $keyword) as $value) - { - if (mb_strlen($value) > 5) - { - if (!in_array(mb_strtolower($value), array_map('strtolower', $stopWords))) - { - $result[] = sprintf('@title "%s" | @dn "%s"', $value, $value); - } - } - } - - if (empty($result)) - { - return '*'; - } - else - { - return implode(' | ', $result); - } - - break; - - default: - - $result = []; - - foreach ((array) explode(' ', $keyword) as $value) - { - if (!in_array(mb_strtolower($value), $stopWords)) - { - $result[] = sprintf('@"*%s*"', $value); - } - } - - return implode(' | ', $result); - } - } -} diff --git a/src/app/model/validator.php b/src/app/model/validator.php deleted file mode 100644 index 9040953..0000000 --- a/src/app/model/validator.php +++ /dev/null @@ -1,2216 +0,0 @@ -_config = $config; - } - - // Page - - /// Page title - public function getPageTitleRequired() : bool - { - return $this->_config->page->title->required; - } - - public function getPageTitleLengthMin() : int - { - return $this->_config->page->title->length->min; - } - - public function getPageTitleLengthMax() : int - { - return $this->_config->page->title->length->max; - } - - public function getPageTitleRegex() : string - { - return $this->_config->page->title->regex; - } - - public function pageTitle(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid page title data type') - ); - - return false; - } - - if (empty($value) && $this->getPageTitleRequired()) - { - array_push( - $error, - _('Page title required') - ); - - return false; - } - - if (!preg_match($this->getPageTitleRegex(), $value)) - { - array_push( - $error, - sprintf( - _('Page title format does not match condition "%s"'), - $this->getPageTitleRegex() - ) - ); - - return false; - } - - if (mb_strlen($value) < $this->getPageTitleLengthMin() || - mb_strlen($value) > $this->getPageTitleLengthMax()) - { - array_push( - $error, - sprintf( - _('Page title out of %s-%s chars range'), - $this->getPageTitleLengthMin(), - $this->getPageTitleLengthMax() - ) - ); - - return false; - } - - return true; - } - - /// Page description - public function getPageDescriptionRequired() : bool - { - return $this->_config->page->description->required; - } - - public function getPageDescriptionLengthMin() : int - { - return $this->_config->page->description->length->min; - } - - public function getPageDescriptionLengthMax() : int - { - return $this->_config->page->description->length->max; - } - - public function getPageDescriptionRegex() : string - { - return $this->_config->page->description->regex; - } - - public function pageDescription(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid page description data type') - ); - - return false; - } - - if (empty($value) && $this->getPageDescriptionRequired()) - { - array_push( - $error, - _('Page description required') - ); - - return false; - } - - if (!preg_match($this->getPageDescriptionRegex(), $value)) - { - array_push( - $error, - sprintf( - _('Page description format does not match condition "%s"'), - $this->getPageDescriptionRegex() - ) - ); - - return false; - } - - if (mb_strlen($value) < $this->getPageDescriptionLengthMin() || - mb_strlen($value) > $this->getPageDescriptionLengthMax()) - { - array_push( - $error, - sprintf( - _('Page description out of %s-%s chars range'), - $this->getPageDescriptionLengthMin(), - $this->getPageDescriptionLengthMax() - ) - ); - - return false; - } - - return true; - } - - /// Page keywords - public function getPageKeywordsRequired() : bool - { - return $this->_config->page->keywords->required; - } - - public function getPageKeywordsQuantityMin() : int - { - return $this->_config->page->keywords->quantity->min; - } - - public function getPageKeywordsQuantityMax() : int - { - return $this->_config->page->keywords->quantity->max; - } - - public function pageKeywords(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid page keywords data type') - ); - - return false; - } - - if (empty($value) && $this->getPageKeywordsRequired()) - { - array_push( - $error, - _('Page keywords required') - ); - - return false; - } - - if ($this->getPageKeywordsRequired()) - { - $total = 0; - - foreach (explode(PHP_EOL, str_replace(['#', ',', ' '], PHP_EOL, $value)) as $keyword) - { - $error = []; - - if (!$this->pageKeyword($keyword, $error)) - { - array_push( - $error, - _('Invalid page keyword'), - ); - - return false; - } - - $total++; - } - - if ($total < $this->getPageKeywordsQuantityMin() || - $total > $this->getPageKeywordsQuantityMax()) - { - array_push( - $error, - sprintf( - _('Page keywords quantity out of %s-%s range'), - $this->getPageKeywordsQuantityMin(), - $this->getPageKeywordsQuantityMax() - ) - ); - - return false; - } - } - - return true; - } - - /// Page keyword - public function getPageKeywordLengthMin() : int - { - return $this->_config->page->keyword->length->min; - } - - public function getPageKeywordLengthMax() : int - { - return $this->_config->page->keyword->length->max; - } - - public function getPageKeywordRegex() : string - { - return $this->_config->page->keyword->regex; - } - - public function pageKeyword(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid page keyword data type') - ); - - return false; - } - - if (!preg_match($this->getPageKeywordRegex(), $value)) - { - array_push( - $error, - sprintf( - _('Page keyword format does not match condition "%s"'), - $this->getPageKeywordRegex() - ) - ); - - return false; - } - - if (mb_strlen($value) < $this->getPageKeywordLengthMin() || - mb_strlen($value) > $this->getPageKeywordLengthMax()) - { - array_push( - $error, - sprintf( - _('Page keyword out of %s-%s chars range'), - $this->getPageKeywordLengthMin(), - $this->getPageKeywordLengthMax() - ) - ); - - return false; - } - - return true; - } - - /// Page image - public function getPageImageRequired() : bool - { - return $this->_config->page->image->required; - } - - public function getPageImageMimeTypes() : array - { - return $this->_config->page->image->mime; - } - - /// Page torrent - public function getPageTorrentRequired() : bool - { - return $this->_config->page->torrent->required; - } - - public function getPageTorrentMimeTypes() : array - { - return $this->_config->page->torrent->mime; - } - - // Common - public function host(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid host data type') - ); - - return false; - } - - if (!filter_var(str_replace(['[',']'], false, $value), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) - { - array_push( - $error, - sprintf( - _('Host "%s" not supported'), - $value - ) - ); - - return false; - } - - if (!preg_match($this->_config->host->regex, str_replace(['[',']'], false, $value))) - { - array_push( - $error, - sprintf( - _('Host "%s" not match condition "%s"'), - $value, - $this->_config->host->regex - ) - ); - - return false; - } - - return true; - } - - public function url(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid URL data type') - ); - - return false; - } - - if (!filter_var($value, FILTER_VALIDATE_URL)) - { - array_push( - $error, - sprintf( - _('URL "%s" invalid'), - $value - ) - ); - - return false; - } - - if (!$host = parse_url($value, PHP_URL_HOST)) - { - array_push( - $error, - sprintf( - _('Could not init host for URL "%s"'), - $value - ) - ); - - return false; - } - - if (!self::host($host, $error)) - { - array_push( - $error, - sprintf( - _('URL "%s" has not supported host "%s"'), - $value, - $host, - ) - ); - - return false; - } - - return true; - } - - // User - public function user(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid user data type') - ); - - return false; - } - - // Validate required fields - if (!isset($value->userId) || !self::userId($value->userId, $error) || - !isset($value->address) || !self::userAddress($value->address, $error) || - !isset($value->timeAdded) || !self::userTimeAdded($value->timeAdded, $error) || - !isset($value->timeUpdated) || !self::userTimeUpdated($value->timeUpdated, $error) || - !isset($value->approved) || !self::userApproved($value->approved, $error) || - - (isset($value->public) && !self::userPublic($value->public, $error))) - { - array_push( - $error, - _('Invalid user data protocol') - ); - - return false; - } - - return true; - } - - public function userId(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid userId data type') - ); - - return false; - } - - return true; - } - - public function userAddress(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid user address data type') - ); - - return false; - } - - if (!self::host($value, $error)) - { - array_push( - $error, - sprintf( - _('User address "%s" not supported'), - $value - ) - ); - - return false; - } - - return true; - } - - public function userTimeAdded(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid user timeAdded data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('User timeAdded out of range') - ); - - return false; - } - - return true; - } - - public function userTimeUpdated(mixed $value, array &$error = []) : bool - { - if (!(is_int($value) || is_bool($value))) - { - array_push( - $error, - _('Invalid user timeUpdated data type') - ); - - return false; - } - - if (is_int($value) && ($value > time() || $value < 0)) - { - array_push( - $error, - _('User timeUpdated out of range') - ); - - return false; - } - - return true; - } - - public function userApproved(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid user approved data type') - ); - - return false; - } - - return true; - } - - public function userPublic(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid user public data type') - ); - - return false; - } - - return true; - } - - // Magnet - public function magnet(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet data type') - ); - - return false; - } - - // Validate required fields by protocol - if (!isset($value->userId) || !self::userId($value->userId, $error) || - - !isset($value->magnetId) || !self::magnetId($value->magnetId, $error) || - - - !isset($value->title) || !self::magnetTitle($value->title, $error) || - !isset($value->preview) || !self::magnetPreview($value->preview, $error) || - !isset($value->description) || !self::magnetDescription($value->description, $error) || - - !isset($value->comments) || !self::magnetComments($value->comments, $error) || - !isset($value->sensitive) || !self::magnetSensitive($value->sensitive, $error) || - !isset($value->approved) || !self::magnetApproved($value->approved, $error) || - - !isset($value->timeAdded) || !self::magnetTimeAdded($value->timeAdded, $error) || - !isset($value->timeUpdated) || !self::magnetTimeUpdated($value->timeUpdated, $error) || - - !isset($value->dn) || !self::magnetDn($value->dn, $error) || - !isset($value->xt) || !self::magnetXt($value->xt, $error) || - - !isset($value->xl) || !self::magnetXl($value->xl, $error) || - - !isset($value->kt) || !self::magnetKt($value->kt, $error) || - !isset($value->tr) || !self::magnetTr($value->tr, $error) || - !isset($value->as) || !self::magnetAs($value->as, $error) || - !isset($value->xs) || !self::magnetWs($value->xs, $error) || - - (isset($value->public) && !self::magnetPublic($value->public, $error))) - { - array_push( - $error, - _('Invalid magnet data protocol') - ); - - return false; - } - - return true; - } - - public function magnetId(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnetId data type') - ); - - return false; - } - - return true; - } - - public function magnetTitle(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid magnet title data type') - ); - - return false; - } - - if (!preg_match(MAGNET_TITLE_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Magnet title format does not match condition "%s"'), - MAGNET_TITLE_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < MAGNET_TITLE_MIN_LENGTH || - mb_strlen($value) > MAGNET_TITLE_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Magnet title out of %s-%s chars range'), - MAGNET_TITLE_MIN_LENGTH, - MAGNET_TITLE_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function magnetPreview(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid magnet preview data type') - ); - - return false; - } - - if (!preg_match(MAGNET_PREVIEW_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Magnet preview format does not match condition "%s"'), - MAGNET_PREVIEW_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < MAGNET_PREVIEW_MIN_LENGTH || - mb_strlen($value) > MAGNET_PREVIEW_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Magnet preview out of %s-%s chars range'), - MAGNET_PREVIEW_MIN_LENGTH, - MAGNET_PREVIEW_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function magnetDescription(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid magnet description data type') - ); - - return false; - } - - if (!preg_match(MAGNET_DESCRIPTION_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Magnet description format does not match condition "%s"'), - MAGNET_DESCRIPTION_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < MAGNET_DESCRIPTION_MIN_LENGTH || - mb_strlen($value) > MAGNET_DESCRIPTION_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Magnet description out of %s-%s chars range'), - MAGNET_DESCRIPTION_MIN_LENGTH, - MAGNET_DESCRIPTION_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function magnetComments(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet comments data type') - ); - - return false; - } - - return true; - } - - public function magnetPublic(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet public data type') - ); - - return false; - } - - return true; - } - - public function magnetApproved(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet approved data type') - ); - - return false; - } - - return true; - } - - public function magnetSensitive(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet sensitive data type') - ); - - return false; - } - - return true; - } - - public function magnetTimeAdded(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnet timeAdded data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('Magnet timeAdded out of range') - ); - - return false; - } - - return true; - } - - public function magnetTimeUpdated(mixed $value, array &$error = []) : bool - { - if (!(is_int($value) || is_bool($value))) - { - array_push( - $error, - _('Invalid magnet timeUpdated data type') - ); - - return false; - } - - if (is_int($value) && ($value > time() || $value < 0)) - { - array_push( - $error, - _('Magnet timeUpdated out of range') - ); - - return false; - } - - return true; - } - - public function magnetDn(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid magnet display name data type') - ); - - return false; - } - - if (!preg_match(MAGNET_DN_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Magnet display name format does not match condition "%s"'), - MAGNET_DN_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < MAGNET_DN_MIN_LENGTH || - mb_strlen($value) > MAGNET_DN_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Magnet display name out of %s-%s chars range'), - MAGNET_DN_MIN_LENGTH, - MAGNET_DN_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function magnetXl(mixed $value, array &$error = []) : bool - { - if (!(is_int($value) || is_float($value))) - { - array_push( - $error, - _('Invalid magnet exact length data type') - ); - - return false; - } - - return true; - } - - public function magnetKt(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet keyword data type') - ); - - return false; - } - - $total = 0; - - foreach ($value as $kt) - { - if (!is_string($kt)) - { - array_push( - $error, - _('Invalid magnet keyword value data type') - ); - - return false; - } - - if (!preg_match(MAGNET_KT_REGEX, $kt)) - { - array_push( - $error, - sprintf( - _('Magnet keyword format does not match condition "%s"'), - MAGNET_KT_REGEX - ) - ); - - return false; - } - - if (mb_strlen($kt) < MAGNET_KT_MIN_LENGTH || - mb_strlen($kt) > MAGNET_KT_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Magnet keyword out of %s-%s chars range'), - MAGNET_KT_MIN_LENGTH, - MAGNET_KT_MAX_LENGTH - ) - ); - - return false; - } - - $total++; - } - - if ($total < MAGNET_KT_MIN_QUANTITY || - $total > MAGNET_KT_MAX_QUANTITY) - { - array_push( - $error, - sprintf( - _('Magnet keywords quantity out of %s-%s range'), - MAGNET_KT_MIN_QUANTITY, - MAGNET_KT_MAX_QUANTITY - ) - ); - - return false; - } - - return true; - } - - public function magnetXt(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet info hash data type') - ); - - return false; - } - - foreach ($value as $xt) - { - if (empty($xt->version)) - { - array_push( - $error, - _('Magnet info hash version required') - ); - - return false; - } - - if (!(is_int($xt->version) || is_float($xt->version))) - { - array_push( - $error, - _('Invalid magnet info hash version data type') - ); - - return false; - } - - if (empty($xt->value)) - { - array_push( - $error, - _('Magnet info hash value required') - ); - - return false; - } - - if (!is_string($xt->value)) - { - array_push( - $error, - _('Invalid magnet info hash value data type') - ); - - return false; - } - - switch ($xt->version) - { - case 1: - - if (!preg_match('/^([A-z0-9]{40})$/i', $xt->value)) - { - array_push( - $error, - _('Invalid magnet info hash v1 value') - ); - - return false; - } - - break; - - case 2: - - if (!preg_match('/^([A-z0-9]{64})$/i', $xt->value)) - { - array_push( - $error, - _('Invalid magnet info hash v2 value') - ); - - return false; - } - - break; - - default: - - array_push( - $error, - _('Magnet info hash version not supported') - ); - - return false; - } - } - - return true; - } - - public function magnetTr(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet address tracker data type') - ); - - return false; - } - - $total = 0; - - foreach ($value as $tr) - { - if (!self::url($tr, $error)) - { - array_push( - $error, - sprintf( - _('Invalid magnet address tracker URL "%s"'), - $tr - ) - ); - - return false; - } - - $total++; - } - - if ($total < MAGNET_TR_MIN_QUANTITY || - $total > MAGNET_TR_MAX_QUANTITY) - { - array_push( - $error, - sprintf( - _('Magnet address trackers quantity out of %s-%s range'), - MAGNET_TR_MIN_QUANTITY, - MAGNET_TR_MAX_QUANTITY - ) - ); - - return false; - } - - return true; - } - - public function magnetAs(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet acceptable source data type') - ); - - return false; - } - - $total = 0; - - foreach ($value as $as) - { - if (!self::url($as, $error)) - { - array_push( - $error, - sprintf( - _('Invalid magnet acceptable source URL "%s"'), - $as - ) - ); - - return false; - } - - $total++; - } - - if ($total < MAGNET_AS_MIN_QUANTITY || - $total > MAGNET_AS_MAX_QUANTITY) - { - array_push( - $error, - sprintf( - _('Magnet acceptable sources quantity out of %s-%s range'), - MAGNET_AS_MIN_QUANTITY, - MAGNET_AS_MAX_QUANTITY - ) - ); - - return false; - } - - return true; - } - - public function magnetWs(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet web seed data type') - ); - - return false; - } - - $total = 0; - - foreach ($value as $ws) - { - if (!self::url($ws, $error)) - { - array_push( - $error, - sprintf( - _('Invalid magnet web seed URL "%s"'), - $ws - ) - ); - - return false; - } - - $total++; - } - - if ($total < MAGNET_WS_MIN_QUANTITY || - $total > MAGNET_WS_MAX_QUANTITY) - { - array_push( - $error, - sprintf( - _('Magnet web seeds quantity out of %s-%s range'), - MAGNET_WS_MIN_QUANTITY, - MAGNET_WS_MAX_QUANTITY - ) - ); - - return false; - } - - return true; - } - - // Magnet comment - public function magnetComment(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet comment data type') - ); - - return false; - } - - if (!isset($value->magnetCommentId) || !self::magnetCommentId($value->magnetCommentId, $error) || - !isset($value->magnetId) || !self::magnetId($value->magnetId, $error) || - !isset($value->userId) || !self::userId($value->userId, $error) || - !isset($value->timeAdded) || !self::magnetCommentTimeAdded($value->timeAdded, $error) || - !isset($value->approved) || !self::magnetCommentApproved($value->approved, $error) || - !isset($value->value) || !self::magnetCommentValue($value->value, $error) || - - (isset($value->magnetCommentIdParent) && !self::magnetCommentIdParent($value->magnetCommentIdParent, $error)) || - - (isset($value->public) && !self::magnetCommentPublic($value->public, $error))) - { - array_push( - $error, - _('Invalid magnet comment data protocol') - ); - - return false; - } - - return true; - } - - public function magnetCommentId(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnetCommentId data type') - ); - - return false; - } - - return true; - } - - public function magnetCommentIdParent(mixed $value, array &$error = []) : bool - { - if (!(is_null($value) || is_int($value))) - { - array_push( - $error, - _('Invalid magnet magnetCommentIdParent data type') - ); - - return false; - } - - if (is_int($value) && !self::magnetCommentId($value, $error)) - { - return false; - } - - return true; - } - - public function magnetCommentTimeAdded(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnet comment timeAdded data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('Magnet comment timeAdded out of range') - ); - - return false; - } - - return true; - } - - public function magnetCommentApproved(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet comment approved data type') - ); - - return false; - } - - return true; - } - - public function magnetCommentPublic(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet comment public data type') - ); - - return false; - } - - return true; - } - - public function magnetCommentValue(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid magnet comment value data type') - ); - - return false; - } - - if (mb_strlen($value) < MAGNET_COMMENT_MIN_LENGTH || - mb_strlen($value) > MAGNET_COMMENT_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Magnet comment value out of %s-%s chars range'), - MAGNET_COMMENT_MIN_LENGTH, - MAGNET_COMMENT_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - // Magnet download - public function magnetDownload(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet download data type') - ); - - return false; - } - - if (!isset($value->magnetDownloadId) || !self::magnetDownloadId($value->magnetDownloadId, $error) || - !isset($value->magnetId) || !self::magnetId($value->magnetId, $error) || - !isset($value->userId) || !self::userId($value->userId, $error) || - !isset($value->timeAdded) || !self::magnetDownloadTimeAdded($value->timeAdded, $error) - ) - { - array_push( - $error, - _('Invalid magnet download data protocol') - ); - - return false; - } - - return true; - } - - public function magnetDownloadId(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnetDownloadId data type') - ); - - return false; - } - - return true; - } - - public function magnetDownloadTimeAdded(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnet download timeAdded data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('Magnet download timeAdded out of range') - ); - - return false; - } - - return true; - } - - // Magnet star - public function magnetStar(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet star data type') - ); - - return false; - } - - if (!isset($value->magnetStarId) || !self::magnetViewId($value->magnetStarId, $error) || - !isset($value->magnetId) || !self::magnetId($value->magnetId, $error) || - !isset($value->userId) || !self::userId($value->userId, $error) || - !isset($value->timeAdded) || !self::magnetStarTimeAdded($value->timeAdded, $error) || - !isset($value->value) || !self::magnetStarValue($value->value, $error) - ) - { - array_push( - $error, - _('Invalid magnet star data protocol') - ); - - return false; - } - - return true; - } - - public function magnetStarId(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnetStarId data type') - ); - - return false; - } - - return true; - } - - public function magnetStarValue(mixed $value, array &$error = []) : bool - { - if (!is_bool($value)) - { - array_push( - $error, - _('Invalid magnet star value data type') - ); - - return false; - } - - return true; - } - - public function magnetStarTimeAdded(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnet star timeAdded data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('Magnet star timeAdded out of range') - ); - - return false; - } - - return true; - } - - // Magnet view - public function magnetView(mixed $value, array &$error = []) : bool - { - if (!is_object($value)) - { - array_push( - $error, - _('Invalid magnet view data type') - ); - - return false; - } - - if (!isset($value->magnetViewId) || !self::magnetViewId($value->magnetViewId, $error) || - !isset($value->magnetId) || !self::magnetId($value->magnetId, $error) || - !isset($value->userId) || !self::userId($value->userId, $error) || - !isset($value->timeAdded) || !self::magnetViewTimeAdded($value->timeAdded, $error) - ) - { - array_push( - $error, - _('Invalid magnet view data protocol') - ); - - return false; - } - - return true; - } - - public function magnetViewId(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnetViewId data type') - ); - - return false; - } - - return true; - } - - public function magnetViewTimeAdded(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid magnet view timeAdded data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('Magnet view timeAdded out of range') - ); - - return false; - } - - return true; - } - - // Torrent - public function torrentAnnounce(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid torrent announce data type') - ); - - return false; - } - - if (!self::url($tr, $error)) - { - array_push( - $error, - sprintf( - _('Invalid torrent announce URL "%s"'), - $tr - ) - ); - - return false; - } - - return true; - } - - public function torrentAnnounceList(mixed $value, array &$error = []) : bool - { - if (!is_array($value)) - { - array_push( - $error, - _('Invalid torrent announce data type') - ); - - return false; - } - - $total = 0; - - foreach ($value as $list) - { - if (!is_array($list)) - { - array_push( - $error, - _('Invalid torrent announce list') - ); - - return false; - } - - foreach ($list as $announce) - { - if (!self::torrentAnnounce($announce, $error)) - { - array_push( - $error, - sprintf( - _('Invalid torrent announce list URL "%s"'), - $announce - ) - ); - - return false; - } - - $total++; - } - } - - if ($total < TORRENT_ANNOUNCE_MIN_QUANTITY || - $total > TORRENT_ANNOUNCE_MAX_QUANTITY) - { - array_push( - $error, - sprintf( - _('Torrent announces quantity out of %s-%s range'), - TORRENT_ANNOUNCE_MIN_QUANTITY, - TORRENT_ANNOUNCE_MAX_QUANTITY - ) - ); - - return false; - } - - return true; - } - - public function torrentComment(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid torrent comment data type') - ); - - return false; - } - - if (!preg_match(TORRENT_COMMENT_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Torrent comment format does not match condition "%s"'), - TORRENT_COMMENT_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < TORRENT_COMMENT_MIN_LENGTH || - mb_strlen($value) > TORRENT_COMMENT_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Torrent comment out of %s-%s chars range'), - TORRENT_COMMENT_MIN_LENGTH, - TORRENT_COMMENT_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function torrentCreatedBy(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid torrent created by data type') - ); - - return false; - } - - if (!preg_match(TORRENT_CREATED_BY_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Torrent created by format does not match condition "%s"'), - TORRENT_CREATED_BY_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < TORRENT_CREATED_BY_MIN_LENGTH || - mb_strlen($value) > TORRENT_CREATED_BY_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Torrent created by out of %s-%s chars range'), - TORRENT_CREATED_BY_MIN_LENGTH, - TORRENT_CREATED_BY_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function torrentCreationDate(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid torrent creation date data type') - ); - - return false; - } - - if ($value > time() || $value < 0) - { - array_push( - $error, - _('Torrent creation date out of range') - ); - - return false; - } - - return true; - } - - public function torrentInfo(mixed $value, array &$error = []) : bool - { - if (!is_array($value)) - { - array_push( - $error, - _('Invalid torrent info data type') - ); - - return false; - } - - if (empty($value)) - { - array_push( - $error, - _('Torrent info has no keys') - ); - - return false; - } - - foreach ($value as $info) - { - if (!is_array($info)) - { - array_push( - $error, - _('Invalid torrent info protocol') - ); - - return false; - } - - if (empty($info)) - { - array_push( - $error, - _('Torrent info has no values') - ); - - return false; - } - - foreach ($info as $key => $data) - { - switch ($key) - { - case 'file-duration': - - if (!self::torrentInfoFileDuration($data, $error)) - { - array_push( - $error, - _('Invalid torrent info file-duration') - ); - - return false; - } - - break; - case 'file-media': - - if (!self::torrentInfoFileMedia($data, $error)) - { - array_push( - $error, - _('Invalid torrent info file-media') - ); - - return false; - } - - break; - case 'files': - - if (!self::torrentInfoFiles($data, $error)) - { - array_push( - $error, - _('Invalid torrent info files') - ); - - return false; - } - - break; - case 'name': - - if (!self::torrentInfoName($data, $error)) - { - array_push( - $error, - _('Invalid torrent info name') - ); - - return false; - } - - break; - case 'piece length': - - if (!self::torrentInfoPieceLength($data, $error)) - { - array_push( - $error, - _('Invalid torrent info piece length') - ); - - return false; - } - - break; - case 'pieces': - - if (!self::torrentInfoPieces($data, $error)) - { - array_push( - $error, - _('Invalid torrent info pieces') - ); - - return false; - } - - break; - case 'private': - - if (!self::torrentInfoPrivate($data, $error)) - { - array_push( - $error, - _('Invalid torrent info private') - ); - - return false; - } - - break; - case 'profiles': - - if (!self::torrentInfoProfiles($data, $error)) - { - array_push( - $error, - _('Invalid torrent info profiles') - ); - - return false; - } - - break; - case 'source': - - if (!self::torrentInfoSource($data, $error)) - { - array_push( - $error, - _('Invalid torrent info source') - ); - - return false; - } - - break; - default: - array_push( - $error, - _('Not supported torrent info key') - ); - } - } - } - - return true; - } - - public function torrentInfoName(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid torrent info name data type') - ); - - return false; - } - - if (!preg_match(TORRENT_INFO_NAME_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Torrent info name format does not match condition "%s"'), - TORRENT_INFO_NAME_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < TORRENT_INFO_NAME_MIN_LENGTH || - mb_strlen($value) > TORRENT_INFO_NAME_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Torrent info name out of %s-%s chars range'), - TORRENT_INFO_NAME_MIN_LENGTH, - TORRENT_INFO_NAME_MAX_LENGTH - ) - ); - - return false; - } - - return true; - } - - public function torrentInfoSource(mixed $value, array &$error = []) : bool - { - if (!is_string($value)) - { - array_push( - $error, - _('Invalid torrent info source data type') - ); - - return false; - } - - if (!preg_match(TORRENT_INFO_SOURCE_REGEX, $value)) - { - array_push( - $error, - sprintf( - _('Torrent info source format does not match condition "%s"'), - TORRENT_INFO_SOURCE_REGEX - ) - ); - - return false; - } - - if (mb_strlen($value) < TORRENT_INFO_SOURCE_MIN_LENGTH || - mb_strlen($value) > TORRENT_INFO_SOURCE_MAX_LENGTH) - { - array_push( - $error, - sprintf( - _('Torrent info source out of %s-%s chars range'), - TORRENT_INFO_SOURCE_MIN_LENGTH, - TORRENT_INFO_SOURCE_MAX_LENGTH - ) - ); - - return false; - } - - return true; - - return true; - } - - public function torrentInfoFileDuration(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid torrent file-duration data type') - ); - - return false; - } - - if ($value < 0) - { - array_push( - $error, - _('Torrent file-duration out of range') - ); - - return false; - } - - return true; - } - - public function torrentInfoPieceLength(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid torrent info piece length data type') - ); - - return false; - } - - if ($value < 0) - { - array_push( - $error, - _('Torrent torrent info piece length out of range') - ); - - return false; - } - - return true; - } - - public function torrentInfoPieces(mixed $value, array &$error = []) : bool - { - // @TODO - - return true; - } - - public function torrentInfoPrivate(mixed $value, array &$error = []) : bool - { - if (!is_int($value)) - { - array_push( - $error, - _('Invalid torrent info private data type') - ); - - return false; - } - - if (!in_array($value, [0, 1])) - { - array_push( - $error, - _('Invalid torrent info private value') - ); - - return false; - } - - return true; - } - - public function torrentInfoProfiles(mixed $value, array &$error = []) : bool - { - // @TODO - - return true; - } - - public function torrentInfoFileMedia(mixed $value, array &$error = []) : bool - { - // @TODO - - return true; - } - - public function torrentInfoFiles(mixed $value, array &$error = []) : bool - { - // @TODO - - return true; - } -} \ No newline at end of file diff --git a/src/app/model/website.php b/src/app/model/website.php deleted file mode 100644 index f1d4f9f..0000000 --- a/src/app/model/website.php +++ /dev/null @@ -1,46 +0,0 @@ -_config = $config; - } - - public function getConfig() : object - { - return $this->_config->name; - } - - public function getName() : string - { - return $this->_config->name; - } - - public function getUrl() : string - { - return $this->_config->url; - } - - public function getDefaultLocale() : string - { - return $this->_config->default->locale; - } - - public function getDefaultUserStatus() : bool - { - return $this->_config->default->user->status; - } - - public function getDefaultUserApproved() : bool - { - return $this->_config->default->user->approved; - } - - public function getApiExportEnabled() : bool - { - return $this->_config->api->export->enabled; - } -} \ No newline at end of file diff --git a/src/app/view/theme/default/index.phtml b/src/app/view/theme/default/index.phtml deleted file mode 100644 index 4585236..0000000 --- a/src/app/view/theme/default/index.phtml +++ /dev/null @@ -1,32 +0,0 @@ - - - render() ?> - - render() ?> -
-
-
-
- render() ?> - - - render($page->pageId) ?> - - render() ?> - -
-

- -

-
- -
-
- -
-
-
-
- render() ?> - - \ No newline at end of file diff --git a/src/app/view/theme/default/module/footer.phtml b/src/app/view/theme/default/module/footer.phtml deleted file mode 100644 index 0064221..0000000 --- a/src/app/view/theme/default/module/footer.phtml +++ /dev/null @@ -1,27 +0,0 @@ -
-
-
-
- $tracker) { ?> - - / - - | - - - | - - | - - - | - - - | - -
-
-
-
- - \ No newline at end of file diff --git a/src/app/view/theme/default/module/head.phtml b/src/app/view/theme/default/module/head.phtml deleted file mode 100644 index 70c6452..0000000 --- a/src/app/view/theme/default/module/head.phtml +++ /dev/null @@ -1,7 +0,0 @@ - - - <?php echo $title ?> - - - - \ No newline at end of file diff --git a/src/app/view/theme/default/module/header.phtml b/src/app/view/theme/default/module/header.phtml deleted file mode 100644 index 1c622e9..0000000 --- a/src/app/view/theme/default/module/header.phtml +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- - render() ?> -
-
-
\ No newline at end of file diff --git a/src/app/view/theme/default/module/page.phtml b/src/app/view/theme/default/module/page.phtml deleted file mode 100644 index f242004..0000000 --- a/src/app/view/theme/default/module/page.phtml +++ /dev/null @@ -1,126 +0,0 @@ - -
-
- -

- - - - - -
-
- - - - - - - - - - - - - -
- preview) { ?> -
preview ?>
- - keywords) { ?> -
- keywords as $keyword) { ?> - - # - - -
- -
- - - timeUpdated ? _('Updated') : _('Added') ?> - timeUpdated ? $magnet->timeUpdated : $magnet->timeAdded ?> - - - - - - - seeders ?> - - - - - - completed ?> - - - - - - - leechers ?> - - directs) { ?> - - - - - directs ?> - - - - - star->status) { ?> - - - - - - - - - - star->total ?> - - comments) { ?> - - - comment->status) { ?> - - - - - - - - - - comment->total ?> - - - - - download->status) { ?> - - - - - - - - - - download->total ?> - -
-
\ No newline at end of file diff --git a/src/app/view/theme/default/module/pagination.phtml b/src/app/view/theme/default/module/pagination.phtml deleted file mode 100644 index 1f8422a..0000000 --- a/src/app/view/theme/default/module/pagination.phtml +++ /dev/null @@ -1,15 +0,0 @@ -
-
- page, $pagination->total) ?> - back) { ?> - - - - - next) { ?> - - - - -
-
\ No newline at end of file diff --git a/src/app/view/theme/default/module/search.phtml b/src/app/view/theme/default/module/search.phtml deleted file mode 100644 index 1e62af4..0000000 --- a/src/app/view/theme/default/module/search.phtml +++ /dev/null @@ -1,20 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/src/app/view/theme/default/page/form/submit.phtml b/src/app/view/theme/default/page/form/submit.phtml deleted file mode 100644 index 629b4f8..0000000 --- a/src/app/view/theme/default/page/form/submit.phtml +++ /dev/null @@ -1,140 +0,0 @@ - - - render() ?> - - render() ?> -
-
-
-
- render() ?> -
-
-

-
-
- -
- - - - - - - -
-
- - - - - - - title->error as $errors) { ?> - -
- -
- - - title->attribute->required ? 'required="required"' : false ?> - value="title->attribute->value ?>" - placeholder="title->attribute->placeholder ?>" - minlength="title->attribute->minlength ?>" - maxlength="title->attribute->maxlength ?>" /> -
-
- - - - - - - description->error as $errors) { ?> - -
- -
- - - -
-
- - - - - - - keywords->error as $errors) { ?> - -
- -
- - - -
-
- sensitive->attribute->value ? 'checked="checked"' : false ?> /> - - - - - - -
-
- -
-
-
-
-
-
-
- render() ?> - - \ No newline at end of file diff --git a/src/app/view/theme/default/response.phtml b/src/app/view/theme/default/response.phtml deleted file mode 100644 index 2438208..0000000 --- a/src/app/view/theme/default/response.phtml +++ /dev/null @@ -1,24 +0,0 @@ - - - render() ?> - - render() ?> -
-
-
-
-
-

- -

-
- -
-
-
-
-
-
- render() ?> - - \ No newline at end of file diff --git a/src/config/bootstrap.php b/src/config/bootstrap.php deleted file mode 100644 index c2e56e6..0000000 --- a/src/config/bootstrap.php +++ /dev/null @@ -1,160 +0,0 @@ -render(); - - break; - - case 'views': - - require_once __DIR__ . '/../app/controller/views.php'; - - $appControllerViews = new AppControllerViews(); - - $appControllerViews->render(); - - break; - - case 'downloads': - - require_once __DIR__ . '/../app/controller/downloads.php'; - - $appControllerDownloads = new AppControllerDownloads(); - - $appControllerDownloads->render(); - - break; - - case 'comments': - - require_once __DIR__ . '/../app/controller/comments.php'; - - $appControllerComments = new AppControllerComments(); - - $appControllerComments->render(); - - break; - - case 'editions': - - require_once __DIR__ . '/../app/controller/editions.php'; - - $appControllerEditions = new AppControllerEditions(); - - $appControllerEditions->render(); - - break; - - case 'submit': - - require_once __DIR__ . '/../app/model/database.php'; - require_once __DIR__ . '/../app/model/validator.php'; - require_once __DIR__ . '/../app/model/locale.php'; - require_once __DIR__ . '/../app/model/website.php'; - require_once __DIR__ . '/../app/model/session.php'; - require_once __DIR__ . '/../app/model/request.php'; - - require_once __DIR__ . '/../app/controller/page.php'; - - $appControllerPage = new AppControllerPage( - new AppModelDatabase( - Environment::config('database') - ), - new AppModelValidator( - Environment::config('validator') - ), - new AppModelLocale( - Environment::config('locales') - ), - new AppModelWebsite( - Environment::config('website') - ), - new AppModelSession( - $_SERVER['REMOTE_ADDR'] - ), - new AppModelRequest( - $_GET, - $_POST, - $_FILES - ) - ); - - $appControllerPage->renderFormSubmit(); - - break; - - default: - - require_once __DIR__ . '/../app/controller/response.php'; - - $appControllerResponse = new AppControllerResponse( - sprintf( - _('404 - Not found - %s'), - Environment::config('website')->name - ), - _('404'), - _('Page not found'), - 404 - ); - - $appControllerResponse->render(); - } -} - -else -{ - require_once __DIR__ . '/../app/model/database.php'; - require_once __DIR__ . '/../app/model/validator.php'; - require_once __DIR__ . '/../app/model/website.php'; - require_once __DIR__ . '/../app/model/session.php'; - - require_once __DIR__ . '/../app/controller/index.php'; - - $appControllerIndex = new AppControllerIndex( - new AppModelDatabase( - Environment::config('database') - ), - new AppModelValidator( - Environment::config('validator') - ), - new AppModelWebsite( - Environment::config('website') - ), - new AppModelSession( - $_SERVER['REMOTE_ADDR'] - ) - ); - - $appControllerIndex->render(); -} diff --git a/src/config/database.json b/src/config/database.json deleted file mode 100644 index 64d4306..0000000 --- a/src/config/database.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "port":3306, - "host":"127.0.0.1", - "name":"", - "user":"", - "password":"" -} \ No newline at end of file diff --git a/src/config/locales.json b/src/config/locales.json deleted file mode 100644 index c124d4b..0000000 --- a/src/config/locales.json +++ /dev/null @@ -1,277 +0,0 @@ -{ - "af-ZA": - [ - "Afrikaans", - "Afrikaans" - ], - "ar": - [ - "العربية", - "Arabic" - ], - "bg-BG": - [ - "Български", - "Bulgarian" - ], - "ca-AD": - [ - "Català", - "Catalan" - ], - "cs-CZ": - [ - "Čeština", - "Czech" - ], - "cy-GB": - [ - "Cymraeg", - "Welsh" - ], - "da-DK": - [ - "Dansk", - "Danish" - ], - "de-AT": - [ - "Deutsch (Österreich)", - "German (Austria)" - ], - "de-CH": - [ - "Deutsch (Schweiz)", - "German (Switzerland)" - ], - "de-DE": - [ - "Deutsch (Deutschland)", - "German (Germany)" - ], - "el-GR": - [ - "Ελληνικά", - "Greek" - ], - "en-GB": - [ - "English (UK)", - "English (UK)" - ], - "en-US": - [ - "English (US)", - "English (US)" - ], - "es-CL": - [ - "Español (Chile)", - "Spanish (Chile)" - ], - "es-ES": - [ - "Español (España)", - "Spanish (Spain)" - ], - "es-MX": - [ - "Español (México)", - "Spanish (Mexico)" - ], - "et-EE": - [ - "Eesti keel", - "Estonian" - ], - "eu": - [ - "Euskara", - "Basque" - ], - "fa-IR": - [ - "فارسی", - "Persian" - ], - "fi-FI": - [ - "Suomi", - "Finnish" - ], - "fr-CA": - [ - "Français (Canada)", - "French (Canada)" - ], - "fr-FR": - [ - "Français (France)", - "French (France)" - ], - "gl-ES": - [ - "Galego (Spain)", - "Galician (Spain)" - ], - "he-IL": - [ - "עברית", - "Hebrew" - ], - "hi-IN": - [ - "हिंदी", - "Hindi" - ], - "hr-HR": - [ - "Hrvatski", - "Croatian" - ], - "hu-HU": - [ - "Magyar", - "Hungarian" - ], - "id-ID": - [ - "Bahasa Indonesia", - "Indonesian" - ], - "is-IS": - [ - "Íslenska", - "Icelandic" - ], - "it-IT": - [ - "Italiano", - "Italian" - ], - "ja-JP": - [ - "日本語", - "Japanese" - ], - "km-KH": - [ - "ភាសាខ្មែរ", - "Khmer" - ], - "ko-KR": - [ - "한국어", - "Korean" - ], - "la": - [ - "Latina", - "Latin" - ], - "lt-LT": - [ - "Lietuvių kalba", - "Lithuanian" - ], - "lv-LV": - [ - "Latviešu", - "Latvian" - ], - "mn-MN": - [ - "Монгол", - "Mongolian" - ], - "nb-NO": - [ - "Norsk bokmål", - "Norwegian (Bokmål)" - ], - "nl-NL": - [ - "Nederlands", - "Dutch" - ], - "nn-NO": - [ - "Norsk nynorsk", - "Norwegian (Nynorsk)" - ], - "pl-PL": - [ - "Polski", - "Polish" - ], - "pt-BR": - [ - "Português (Brasil)", - "Portuguese (Brazil)" - ], - "pt-PT": - [ - "Português (Portugal)", - "Portuguese (Portugal)" - ], - "ro-RO": - [ - "Română", - "Romanian" - ], - "ru-RU": - [ - "Русский", - "Russian" - ], - "sk-SK": - [ - "Slovenčina", - "Slovak" - ], - "sl-SI": - [ - "Slovenščina", - "Slovenian" - ], - "sr-RS": - [ - "Српски / Srpski", - "Serbian" - ], - "sv-SE": - [ - "Svenska", - "Swedish" - ], - "th-TH": - [ - "ไทย", - "Thai" - ], - "tr-TR": - [ - "Türkçe", - "Turkish" - ], - "uk-UA": - [ - "Українська", - "Ukrainian" - ], - "vi-VN": - [ - "Tiếng Việt", - "Vietnamese" - ], - "zh-CN": - [ - "中文 (中国大陆)", - "Chinese (PRC)" - ], - "zh-TW": - [ - "中文 (台灣)", - "Chinese (Taiwan)" - ] -} \ No newline at end of file diff --git a/src/config/memcached.json b/src/config/memcached.json deleted file mode 100644 index c6dd071..0000000 --- a/src/config/memcached.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "port": 11211, - "host": "127.0.0.1", - "namespace": "", - "timeout": 3600 -} \ No newline at end of file diff --git a/src/config/moderators.json b/src/config/moderators.json deleted file mode 100644 index b344d1e..0000000 --- a/src/config/moderators.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - "" -] diff --git a/src/config/nodes.json b/src/config/nodes.json deleted file mode 100644 index 50a40d6..0000000 --- a/src/config/nodes.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "description":"YGGtracker instance #1 running latest stable release", - "url":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker", - "manifest":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/api/manifest.json" - }, - { - "description":"YGGtracker instance #2 running latest stable release", - "url":"http://[200:e6fd:bb3c:b354:cd3a:f939:753e:cd72]/yggtracker", - "manifest":"http://[200:e6fd:bb3c:b354:cd3a:f939:753e:cd72]/yggtracker/api/manifest.json" - } -] \ No newline at end of file diff --git a/src/config/peers.json b/src/config/peers.json deleted file mode 100644 index abfa9e3..0000000 --- a/src/config/peers.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "description":"YGGtracker public peer instance without traffic limit", - "url":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggstate", - "address":"tls://94.140.114.241:4708" - } -] \ No newline at end of file diff --git a/src/config/sphinx.json b/src/config/sphinx.json deleted file mode 100644 index 6115883..0000000 --- a/src/config/sphinx.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "port":9306, - "host":"127.0.0.1" -} \ No newline at end of file diff --git a/src/config/themes.json b/src/config/themes.json deleted file mode 100644 index a248bb9..0000000 --- a/src/config/themes.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - "default" -] diff --git a/src/config/trackers.json b/src/config/trackers.json deleted file mode 100644 index f4f546c..0000000 --- a/src/config/trackers.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "description":"YGGtracker instance, yggdrasil-only connections", - "url":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker", - "announce":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]:2023/announce", - "stats":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]:2023/stats", - "scrape":"http://[201:23b4:991a:634d:8359:4521:5576:15b7]:2023/scrape" - }, - { - "description":"Yggdrasil-only torrent tracker, operated by jeff", - "url":false, - "announce":"http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/announce", - "stats":"http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/stats", - "scrape":"http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/scrape" - }, - { - "description":"Yggdrasil torrent tracker, operated by R4SAS", - "url":false, - "announce":"http://[316:c51a:62a3:8b9::5]/announce", - "stats":"http://[316:c51a:62a3:8b9::5]/stats", - "scrape":"http://[316:c51a:62a3:8b9::5]/scrape" - } -] \ No newline at end of file diff --git a/src/config/validator.json b/src/config/validator.json deleted file mode 100644 index 73e2931..0000000 --- a/src/config/validator.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "host": - { - "regex": "/^0{0,1}[2-3][a-f0-9]{0,2}:/" - }, - "page": - { - "title": - { - "required": true, - "length": - { - "min": 10, - "max": 255 - }, - "regex": "/.*/ui" - }, - "description": - { - "required": false, - "length": - { - "min": 0, - "max": 10000 - }, - "regex": "/.*/ui" - }, - "keyword": - { - "length": - { - "min": 0, - "max": 140 - }, - "regex": "/[\\w]+/ui" - }, - "keywords": - { - "required": false, - "quantity": - { - "min": 0, - "max": 20 - } - }, - "image": - { - "required": false, - "mime": [ - "image/png", - "image/gif", - "image/jpeg", - "image/webp" - ], - "quantity": - { - "min": 0, - "max": 20 - } - }, - "torrent": - { - "required": true, - "mime": [ - "application/x-bittorrent" - ], - "quantity": - { - "min": 0, - "max": 20 - } - } - } -} diff --git a/src/config/website.json b/src/config/website.json deleted file mode 100644 index 49cfcc0..0000000 --- a/src/config/website.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name":"YGGtracker", - "scheme":"", - "host":"", - "port":"", - "path":"", - "default": - { - "locale":"en-US", - "user": - { - "status": true, - "approved": false - } - }, - "api": - { - "export": - { - "enabled" : true - } - } -} \ No newline at end of file diff --git a/src/crontab/export/feed.php b/src/crontab/export/feed.php deleted file mode 100644 index be41107..0000000 --- a/src/crontab/export/feed.php +++ /dev/null @@ -1,558 +0,0 @@ - [], - 'time' => [ - 'ISO8601' => date('c'), - 'total' => microtime(true), - ], - 'http' => - [ - 'total' => 0, - ], - 'memory' => - [ - 'start' => memory_get_usage(), - 'total' => 0, - 'peaks' => 0 - ], -]; - -// Define public registry -$public = [ - 'user' => [], - 'magnet' => [], -]; - -// Begin export -try -{ - // Init API folder if not exists - @mkdir(__DIR__ . '/../public/api'); - - // Delete cached feeds - @unlink(__DIR__ . '/../public/api/manifest.json'); - - @unlink(__DIR__ . '/../public/api/users.json'); - @unlink(__DIR__ . '/../public/api/magnets.json'); - @unlink(__DIR__ . '/../public/api/magnetComments.json'); - @unlink(__DIR__ . '/../public/api/magnetDownloads.json'); - @unlink(__DIR__ . '/../public/api/magnetStars.json'); - @unlink(__DIR__ . '/../public/api/magnetViews.json'); - - if (API_EXPORT_ENABLED) - { - // Manifest - $manifest = - [ - 'updated' => time(), - 'version' => (string) API_VERSION, - - 'settings' => (object) - [ - 'YGGDRASIL_HOST_REGEX' => (string) YGGDRASIL_HOST_REGEX, - - 'NODE_RULE_SUBJECT' => (string) NODE_RULE_SUBJECT, - 'NODE_RULE_LANGUAGES' => (string) NODE_RULE_LANGUAGES, - - 'USER_DEFAULT_APPROVED' => (bool) USER_DEFAULT_APPROVED, - 'USER_AUTO_APPROVE_ON_MAGNET_APPROVE' => (bool) USER_AUTO_APPROVE_ON_MAGNET_APPROVE, - 'USER_AUTO_APPROVE_ON_COMMENT_APPROVE' => (bool) USER_AUTO_APPROVE_ON_COMMENT_APPROVE, - 'USER_DEFAULT_IDENTICON' => (string) USER_DEFAULT_IDENTICON, - 'USER_IDENTICON_FIELD' => (string) USER_IDENTICON_FIELD, - - 'MAGNET_DEFAULT_APPROVED' => (bool) MAGNET_DEFAULT_APPROVED, - 'MAGNET_DEFAULT_PUBLIC' => (bool) MAGNET_DEFAULT_PUBLIC, - 'MAGNET_DEFAULT_COMMENTS' => (bool) MAGNET_DEFAULT_COMMENTS, - 'MAGNET_DEFAULT_SENSITIVE' => (bool) MAGNET_DEFAULT_SENSITIVE, - - 'MAGNET_EDITOR_LOCK_TIMEOUT' => (int) MAGNET_EDITOR_LOCK_TIMEOUT, - - 'MAGNET_TITLE_MIN_LENGTH' => (int) MAGNET_TITLE_MIN_LENGTH, - 'MAGNET_TITLE_MAX_LENGTH' => (int) MAGNET_TITLE_MAX_LENGTH, - 'MAGNET_TITLE_REGEX' => (string) MAGNET_TITLE_REGEX, - - 'MAGNET_PREVIEW_MIN_LENGTH' => (int) MAGNET_PREVIEW_MIN_LENGTH, - 'MAGNET_PREVIEW_MAX_LENGTH' => (int) MAGNET_PREVIEW_MAX_LENGTH, - 'MAGNET_PREVIEW_REGEX' => (string) MAGNET_PREVIEW_REGEX, - - 'MAGNET_DESCRIPTION_MIN_LENGTH' => (int) MAGNET_DESCRIPTION_MIN_LENGTH, - 'MAGNET_DESCRIPTION_MAX_LENGTH' => (int) MAGNET_DESCRIPTION_MAX_LENGTH, - 'MAGNET_DESCRIPTION_REGEX' => (string) MAGNET_DESCRIPTION_REGEX, - - 'MAGNET_DN_MIN_LENGTH' => (int) MAGNET_DN_MIN_LENGTH, - 'MAGNET_DN_MAX_LENGTH' => (int) MAGNET_DN_MAX_LENGTH, - 'MAGNET_DN_REGEX' => (string) MAGNET_DN_REGEX, - - 'MAGNET_KT_MIN_LENGTH' => (int) MAGNET_KT_MIN_LENGTH, - 'MAGNET_KT_MAX_LENGTH' => (int) MAGNET_KT_MAX_LENGTH, - 'MAGNET_KT_MIN_QUANTITY' => (int) MAGNET_KT_MIN_QUANTITY, - 'MAGNET_KT_MAX_QUANTITY' => (int) MAGNET_KT_MAX_QUANTITY, - 'MAGNET_KT_REGEX' => (string) MAGNET_KT_REGEX, - - 'MAGNET_TR_MIN_QUANTITY' => (int) MAGNET_TR_MIN_QUANTITY, - 'MAGNET_TR_MAX_QUANTITY' => (int) MAGNET_TR_MAX_QUANTITY, - - 'MAGNET_AS_MIN_QUANTITY' => (int) MAGNET_AS_MIN_QUANTITY, - 'MAGNET_AS_MAX_QUANTITY' => (int) MAGNET_AS_MAX_QUANTITY, - - 'MAGNET_WS_MIN_QUANTITY' => (int) MAGNET_WS_MIN_QUANTITY, - 'MAGNET_WS_MAX_QUANTITY' => (int) MAGNET_WS_MAX_QUANTITY, - - 'MAGNET_COMMENT_DEFAULT_APPROVED' => (bool) MAGNET_COMMENT_DEFAULT_APPROVED, - 'MAGNET_COMMENT_DEFAULT_PUBLIC' => (bool) MAGNET_COMMENT_DEFAULT_PUBLIC, - 'MAGNET_COMMENT_DEFAULT_PUBLIC' => (bool) MAGNET_COMMENT_DEFAULT_PUBLIC, - 'MAGNET_COMMENT_MIN_LENGTH' => (int) MAGNET_COMMENT_MIN_LENGTH, - 'MAGNET_COMMENT_MAX_LENGTH' => (int) MAGNET_COMMENT_MAX_LENGTH, - - 'MAGNET_STOP_WORDS_SIMILAR' => (object) MAGNET_STOP_WORDS_SIMILAR, - - 'API_USER_AGENT' => (string) API_USER_AGENT, - - 'API_EXPORT_ENABLED' => (bool) API_EXPORT_ENABLED, - 'API_EXPORT_PUSH_ENABLED' => (bool) API_EXPORT_PUSH_ENABLED, - 'API_EXPORT_USERS_ENABLED' => (bool) API_EXPORT_USERS_ENABLED, - 'API_EXPORT_MAGNETS_ENABLED' => (bool) API_EXPORT_MAGNETS_ENABLED, - 'API_EXPORT_MAGNET_DOWNLOADS_ENABLED' => (bool) API_EXPORT_MAGNET_DOWNLOADS_ENABLED, - 'API_EXPORT_MAGNET_COMMENTS_ENABLED' => (bool) API_EXPORT_MAGNET_COMMENTS_ENABLED, - 'API_EXPORT_MAGNET_STARS_ENABLED' => (bool) API_EXPORT_MAGNET_STARS_ENABLED, - 'API_EXPORT_MAGNET_STARS_ENABLED' => (bool) API_EXPORT_MAGNET_STARS_ENABLED, - 'API_EXPORT_MAGNET_VIEWS_ENABLED' => (bool) API_EXPORT_MAGNET_VIEWS_ENABLED, - - 'API_IMPORT_ENABLED' => (bool) API_IMPORT_ENABLED, - 'API_IMPORT_PUSH_ENABLED' => (bool) API_IMPORT_PUSH_ENABLED, - 'API_IMPORT_USERS_ENABLED' => (bool) API_IMPORT_USERS_ENABLED, - - 'API_IMPORT_USERS_APPROVED_ONLY' => (bool) API_IMPORT_USERS_APPROVED_ONLY, - 'API_IMPORT_MAGNETS_ENABLED' => (bool) API_IMPORT_MAGNETS_ENABLED, - 'API_IMPORT_MAGNETS_APPROVED_ONLY' => (bool) API_IMPORT_MAGNETS_APPROVED_ONLY, - 'API_IMPORT_MAGNET_DOWNLOADS_ENABLED' => (bool) API_IMPORT_MAGNET_DOWNLOADS_ENABLED, - 'API_IMPORT_MAGNET_COMMENTS_ENABLED' => (bool) API_IMPORT_MAGNET_COMMENTS_ENABLED, - 'API_IMPORT_MAGNET_COMMENTS_APPROVED_ONLY' => (bool) API_IMPORT_MAGNET_COMMENTS_APPROVED_ONLY, - 'API_IMPORT_MAGNET_STARS_ENABLED' => (bool) API_IMPORT_MAGNET_STARS_ENABLED, - 'API_IMPORT_MAGNET_VIEWS_ENABLED' => (bool) API_IMPORT_MAGNET_VIEWS_ENABLED, - ], - 'totals' => (object) - [ - 'magnets' => (object) - [ - 'total' => $db->getMagnetsTotal(), - 'distributed' => $db->getMagnetsTotalByUsersPublic(true), - 'local' => $db->getMagnetsTotalByUsersPublic(false), - ], - 'downloads' => (object) - [ - 'total' => $db->getMagnetDownloadsTotal(), - 'distributed' => $db->findMagnetDownloadsTotalByUsersPublic(true), - 'local' => $db->findMagnetDownloadsTotalByUsersPublic(false), - ], - 'comments' => (object) - [ - 'total' => $db->getMagnetCommentsTotal(), - 'distributed' => $db->findMagnetCommentsTotalByUsersPublic(true), - 'local' => $db->findMagnetCommentsTotalByUsersPublic(false), - ], - 'stars' => (object) - [ - 'total' => $db->getMagnetStarsTotal(), - 'distributed' => $db->findMagnetStarsTotalByUsersPublic(true), - 'local' => $db->findMagnetStarsTotalByUsersPublic(false), - ], - 'views' => (object) - [ - 'total' => $db->getMagnetViewsTotal(), - 'distributed' => $db->findMagnetViewsTotalByUsersPublic(true), - 'local' => $db->findMagnetViewsTotalByUsersPublic(false), - ], - ], - 'import' => (object) - [ - 'push' => API_IMPORT_PUSH_ENABLED ? sprintf('%s/api/push.php', WEBSITE_URL) : false, - ], - 'export' => (object) - [ - 'users' => API_EXPORT_USERS_ENABLED ? sprintf('%s/api/users.json', WEBSITE_URL) : false, - 'magnets' => API_EXPORT_MAGNETS_ENABLED ? sprintf('%s/api/magnets.json', WEBSITE_URL) : false, - 'magnetDownloads' => API_EXPORT_MAGNET_DOWNLOADS_ENABLED ? sprintf('%s/api/magnetDownloads.json', WEBSITE_URL) : false, - 'magnetComments' => API_EXPORT_MAGNET_COMMENTS_ENABLED ? sprintf('%s/api/magnetComments.json', WEBSITE_URL) : false, - 'magnetStars' => API_EXPORT_MAGNET_STARS_ENABLED ? sprintf('%s/api/magnetStars.json', WEBSITE_URL) : false, - 'magnetViews' => API_EXPORT_MAGNET_VIEWS_ENABLED ? sprintf('%s/api/magnetViews.json', WEBSITE_URL) : false, - ], - 'trackers' => (object) json_decode(file_get_contents(__DIR__ . '/../../config/trackers.json')), - 'nodes' => (object) json_decode(file_get_contents(__DIR__ . '/../../config/nodes.json')), - 'peers' => (object) json_decode(file_get_contents(__DIR__ . '/../../config/peers.json')), - ]; - - /// Dump feed - if ($handle = fopen(__DIR__ . '/../../public/api/manifest.json', 'w+')) - { - fwrite($handle, json_encode($manifest)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/manifest.json', 0774); - } - - // Users - if (API_EXPORT_USERS_ENABLED) - { - $users = []; - - foreach ($db->getUsers() as $user) - { - // Dump public data only - if ($user->public) - { - $users[] = (object) - [ - 'userId' => (int) $user->userId, - 'address' => (string) $user->address, - 'timeAdded' => (int) $user->timeAdded, - 'timeUpdated' => (int) $user->timeUpdated, - 'approved' => (bool) $user->approved, - 'magnets' => (int) $db->findMagnetsTotalByUserId($user->userId), - 'downloads' => (int) $db->findMagnetDownloadsTotalByUserId($user->userId), - 'comments' => (int) $db->findMagnetCommentsTotalByUserId($user->userId), - 'stars' => (int) $db->findMagnetStarsTotalByUserId($user->userId), - 'views' => (int) $db->findMagnetViewsTotalByUserId($user->userId), - ]; - } - - // Cache public status - $public['user'][$user->userId] = (bool) $user->public; - } - - /// Dump users feed - if ($handle = fopen(__DIR__ . '/../../public/api/users.json', 'w+')) - { - fwrite($handle, json_encode($users)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/users.json', 0774); - } - } - - // Magnets - if (API_EXPORT_MAGNETS_ENABLED) - { - $magnets = []; - - foreach ($db->getMagnets($user->userId) as $magnet) - { - // Dump public data only - if ($magnet->public && - $public['user'][$magnet->userId]) // After upgrade, some users have not updated their public status. - // Remote node have warning on import, because user info still hidden to init new profile there. - // Stop magnets export without public profile available, even magnet is public. - { - // Info Hash - $xt = []; - foreach ($db->findMagnetToInfoHashByMagnetId($magnet->magnetId) as $result) - { - if ($infoHash = $db->getInfoHash($result->infoHashId)) - { - $xt[] = (object) [ - 'version' => (float) $infoHash->version, - 'value' => (string) $infoHash->value, - ]; - } - } - - // Keyword Topic - $kt = []; - - foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $result) - { - $kt[] = $db->getKeywordTopic($result->keywordTopicId)->value; - } - - // Address Tracker - $tr = []; - foreach ($db->findAddressTrackerByMagnetId($magnet->magnetId) as $result) - { - $addressTracker = $db->getAddressTracker($result->addressTrackerId); - - $scheme = $db->getScheme($addressTracker->schemeId); - $host = $db->getHost($addressTracker->hostId); - $port = $db->getPort($addressTracker->portId); - $uri = $db->getUri($addressTracker->uriId); - - // Yggdrasil host only - if (!Valid::host($host->value)) - { - continue; - } - - $tr[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - } - - // Acceptable Source - $as = []; - - foreach ($db->findAcceptableSourceByMagnetId($magnet->magnetId) as $result) - { - $acceptableSource = $db->getAcceptableSource($result->acceptableSourceId); - - $scheme = $db->getScheme($acceptableSource->schemeId); - $host = $db->getHost($acceptableSource->hostId); - $port = $db->getPort($acceptableSource->portId); - $uri = $db->getUri($acceptableSource->uriId); - - // Yggdrasil host only - if (!Valid::host($host->value)) - { - continue; - } - - $as[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - } - - // Exact Source - $xs = []; - - foreach ($db->findExactSourceByMagnetId($magnet->magnetId) as $result) - { - $eXactSource = $db->getExactSource($result->eXactSourceId); - - $scheme = $db->getScheme($eXactSource->schemeId); - $host = $db->getHost($eXactSource->hostId); - $port = $db->getPort($eXactSource->portId); - $uri = $db->getUri($eXactSource->uriId); - - // Yggdrasil host only - if (!Valid::host($host->value)) - { - continue; - } - - $xs[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - } - - $magnets[] = (object) - [ - 'magnetId' => (int) $magnet->magnetId, - 'userId' => (int) $magnet->userId, - 'title' => (string) $magnet->title, - 'preview' => (string) $magnet->preview, - 'description' => (string) $magnet->description, - 'comments' => (bool) $magnet->comments, - 'sensitive' => (bool) $magnet->sensitive, - 'approved' => (bool) $magnet->approved, - 'timeAdded' => (int) $magnet->timeAdded, - 'timeUpdated' => (int) $magnet->timeUpdated, - 'dn' => (string) $magnet->dn, - 'xl' => (float) $magnet->xl, - 'xt' => (object) $xt, - 'kt' => (object) $kt, - 'tr' => (object) $tr, - 'as' => (object) $as, - 'xs' => (object) $xs, - ]; - } - - // Cache public status - if (!empty($public['user'][$magnet->userId])) - { - $public['magnet'][$magnet->magnetId] = (bool) $magnet->public; - } else { - $public['magnet'][$magnet->magnetId] = false; - } - } - - /// Dump magnets feed - if ($handle = fopen(__DIR__ . '/../../public/api/magnets.json', 'w+')) - { - fwrite($handle, json_encode($magnets)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/magnets.json', 0774); - } - } - - // Magnet downloads - if (API_EXPORT_MAGNET_DOWNLOADS_ENABLED) - { - $magnetDownloads = []; - - foreach ($db->getMagnetDownloads() as $magnetDownload) - { - // Dump public data only - if (!empty($public['magnet'][$magnetDownload->magnetId]) && - !empty($public['user'][$magnetDownload->userId])) - { - $magnetDownloads[] = (object) - [ - 'magnetDownloadId' => (int) $magnetDownload->magnetDownloadId, - 'userId' => (int) $magnetDownload->userId, - 'magnetId' => (int) $magnetDownload->magnetId, - 'timeAdded' => (int) $magnetDownload->timeAdded, - ]; - } - } - - /// Dump feed - if ($handle = fopen(__DIR__ . '/../../public/api/magnetDownloads.json', 'w+')) - { - fwrite($handle, json_encode($magnetDownloads)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/magnetDownloads.json', 0774); - } - } - - // Magnet comments - if (API_EXPORT_MAGNET_COMMENTS_ENABLED) - { - $magnetComments = []; - - foreach ($db->getMagnetComments() as $magnetComment) - { - // Dump public data only - if (!empty($public['magnet'][$magnetComment->magnetId]) && - !empty($public['user'][$magnetComment->userId])) - { - $magnetComments[] = (object) - [ - 'magnetCommentId' => (int) $magnetComment->magnetCommentId, - 'magnetCommentIdParent' => $magnetComment->magnetCommentIdParent, - 'userId' => (int) $magnetComment->userId, - 'magnetId' => (int) $magnetComment->magnetId, - 'timeAdded' => (int) $magnetComment->timeAdded, - 'approved' => (bool) $magnetComment->approved, - 'value' => (string) $magnetComment->value - ]; - } - } - - /// Dump feed - if ($handle = fopen(__DIR__ . '/../../public/api/magnetComments.json', 'w+')) - { - fwrite($handle, json_encode($magnetComments)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/magnetComments.json', 0774); - } - } - - // Magnet stars - if (API_EXPORT_MAGNET_STARS_ENABLED) - { - $magnetStars = []; - - foreach ($db->getMagnetStars() as $magnetStar) - { - // Dump public data only - if (!empty($public['magnet'][$magnetStar->magnetId]) && - !empty($public['user'][$magnetStar->userId])) - { - $magnetStars[] = (object) - [ - 'magnetStarId' => (int) $magnetStar->magnetStarId, - 'userId' => (int) $magnetStar->userId, - 'magnetId' => (int) $magnetStar->magnetId, - 'value' => (bool) $magnetStar->value, - 'timeAdded' => (int) $magnetStar->timeAdded, - ]; - } - } - - /// Dump feed - if ($handle = fopen(__DIR__ . '/../../public/api/magnetStars.json', 'w+')) - { - fwrite($handle, json_encode($magnetStars)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/magnetStars.json', 0774); - } - } - - // Magnet views - if (API_EXPORT_MAGNET_VIEWS_ENABLED) - { - $magnetViews = []; - - foreach ($db->getMagnetViews() as $magnetView) - { - // Dump public data only - if (!empty($public['magnet'][$magnetView->magnetId]) && - !empty($public['user'][$magnetView->userId])) - { - $magnetViews[] = (object) - [ - 'magnetViewId' => (int) $magnetView->magnetViewId, - 'userId' => (int) $magnetView->userId, - 'magnetId' => (int) $magnetView->magnetId, - 'timeAdded' => (int) $magnetView->timeAdded, - ]; - } - } - - /// Dump feed - if ($handle = fopen(__DIR__ . '/../../public/api/magnetViews.json', 'w+')) - { - fwrite($handle, json_encode($magnetViews)); - fclose($handle); - - chmod(__DIR__ . '/../../public/api/magnetViews.json', 0774); - } - } - } - -} catch (EXception $e) { - - var_dump($e); -} - -// Debug output -$debug['time']['total'] = microtime(true) - $debug['time']['total']; - -$debug['memory']['total'] = memory_get_usage() - $debug['memory']['start']; -$debug['memory']['peaks'] = memory_get_peak_usage(); - -$debug['db']['total']['select'] = $db->getDebug()->query->select->total; -$debug['db']['total']['insert'] = $db->getDebug()->query->insert->total; -$debug['db']['total']['update'] = $db->getDebug()->query->update->total; -$debug['db']['total']['delete'] = $db->getDebug()->query->delete->total; - -print_r($debug); - -// Debug log -if (LOG_CRONTAB_EXPORT_FEED_ENABLED) -{ - @mkdir(LOG_DIRECTORY, 0774, true); - - if ($handle = fopen(LOG_DIRECTORY . '/' . LOG_CRONTAB_EXPORT_FEED_FILENAME, 'a+')) - { - fwrite($handle, print_r($debug, true)); - fclose($handle); - - chmod(LOG_DIRECTORY . '/' . LOG_CRONTAB_EXPORT_FEED_FILENAME, 0774); - } -} \ No newline at end of file diff --git a/src/crontab/export/push.php b/src/crontab/export/push.php deleted file mode 100644 index 18ae4e2..0000000 --- a/src/crontab/export/push.php +++ /dev/null @@ -1,506 +0,0 @@ - [], - 'time' => [ - 'ISO8601' => date('c'), - 'total' => microtime(true), - ], - 'http' => - [ - 'total' => 0, - ], - 'memory' => - [ - 'start' => memory_get_usage(), - 'total' => 0, - 'peaks' => 0 - ], -]; - -// Define public registry -$public = [ - 'user' => [], - 'magnet' => [], -]; - -// Push export enabled -if (API_EXPORT_PUSH_ENABLED) -{ - // Get push queue from memory pool - foreach((array) $memoryApiExportPush = $memory->get('api.export.push') as $id => $push) - { - // Init request - $request = []; - - // User request - if (!empty($push->userId) && API_EXPORT_USERS_ENABLED) - { - // Get user info - if ($user = $db->getUser($push->userId)) - { - // Dump public data only - if ($user->public) - { - $request['user'] = (object) - [ - 'userId' => (int) $user->userId, - 'address' => (string) $user->address, - 'timeAdded' => (int) $user->timeAdded, - 'timeUpdated' => (int) $user->timeUpdated, - 'approved' => (bool) $user->approved, - ]; - - // Cache public status - $public['user'][$user->userId] = (bool) $user->public; - } - } - } - - // Magnet request - if (!empty($push->magnetId) && API_EXPORT_MAGNETS_ENABLED) - { - // Get magnet info - if ($magnet = $db->getMagnet($push->magnetId)) - { - if ($magnet->public && - $public['user'][$magnet->userId]) // After upgrade, some users have not updated their public status. - // Remote node have warning on import, because user info still hidden to init new profile there. - // Stop magnets export without public profile available, even magnet is public. - { - // Info Hash - $xt = []; - foreach ($db->findMagnetToInfoHashByMagnetId($magnet->magnetId) as $result) - { - if ($infoHash = $db->getInfoHash($result->infoHashId)) - { - $xt[] = (object) [ - 'version' => (float) $infoHash->version, - 'value' => (string) $infoHash->value, - ]; - } - } - - // Keyword Topic - $kt = []; - - foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $result) - { - $kt[] = $db->getKeywordTopic($result->keywordTopicId)->value; - } - - // Address Tracker - $tr = []; - foreach ($db->findAddressTrackerByMagnetId($magnet->magnetId) as $result) - { - $addressTracker = $db->getAddressTracker($result->addressTrackerId); - - $scheme = $db->getScheme($addressTracker->schemeId); - $host = $db->getHost($addressTracker->hostId); - $port = $db->getPort($addressTracker->portId); - $uri = $db->getUri($addressTracker->uriId); - - // Yggdrasil host only - if (!Valid::host($host->value)) - { - continue; - } - - $tr[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - } - - // Acceptable Source - $as = []; - - foreach ($db->findAcceptableSourceByMagnetId($magnet->magnetId) as $result) - { - $acceptableSource = $db->getAcceptableSource($result->acceptableSourceId); - - $scheme = $db->getScheme($acceptableSource->schemeId); - $host = $db->getHost($acceptableSource->hostId); - $port = $db->getPort($acceptableSource->portId); - $uri = $db->getUri($acceptableSource->uriId); - - // Yggdrasil host only - if (!Valid::host($host->value)) - { - continue; - } - - $as[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - } - - // Exact Source - $xs = []; - - foreach ($db->findExactSourceByMagnetId($magnet->magnetId) as $result) - { - $eXactSource = $db->getExactSource($result->eXactSourceId); - - $scheme = $db->getScheme($eXactSource->schemeId); - $host = $db->getHost($eXactSource->hostId); - $port = $db->getPort($eXactSource->portId); - $uri = $db->getUri($eXactSource->uriId); - - // Yggdrasil host only - if (!Valid::host($host->value)) - { - continue; - } - - $xs[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - } - - $request['magnet'] = (object) - [ - 'magnetId' => (int) $magnet->magnetId, - 'userId' => (int) $magnet->userId, - 'title' => (string) $magnet->title, - 'preview' => (string) $magnet->preview, - 'description' => (string) $magnet->description, - 'comments' => (bool) $magnet->comments, - 'sensitive' => (bool) $magnet->sensitive, - 'approved' => (bool) $magnet->approved, - 'timeAdded' => (int) $magnet->timeAdded, - 'timeUpdated' => (int) $magnet->timeUpdated, - 'dn' => (string) $magnet->dn, - 'xl' => (float) $magnet->xl, - 'xt' => (object) $xt, - 'kt' => (object) $kt, - 'tr' => (object) $tr, - 'as' => (object) $as, - 'xs' => (object) $xs, - ]; - } - - // Cache public status - if (!empty($public['user'][$magnet->userId])) - { - $public['magnet'][$magnet->magnetId] = (bool) $magnet->public; - } else { - $public['magnet'][$magnet->magnetId] = false; - } - } - } - - // Magnet download request - if (!empty($push->magnetDownloadId) && API_EXPORT_MAGNET_DOWNLOADS_ENABLED) - { - // Get magnet download info - if ($magnetDownload = $db->getMagnetDownload($push->magnetDownloadId)) - { - // Dump public data only - if (!empty($public['magnet'][$magnetDownload->magnetId]) && - !empty($public['user'][$magnetDownload->userId])) - { - $request['magnetDownload'] = (object) - [ - 'magnetDownloadId' => (int) $magnetDownload->magnetDownloadId, - 'userId' => (int) $magnetDownload->userId, - 'magnetId' => (int) $magnetDownload->magnetId, - 'timeAdded' => (int) $magnetDownload->timeAdded, - ]; - } - } - } - - // Magnet comment request - if (!empty($push->magnetCommentId) && API_EXPORT_MAGNET_COMMENTS_ENABLED) - { - // Get magnet comment info - if ($magnetComment = $db->getMagnetComment($push->magnetCommentId)) - { - // Dump public data only - if (!empty($public['magnet'][$magnetComment->magnetId]) && - !empty($public['user'][$magnetComment->userId])) - { - $request['magnetComment'] = (object) - [ - 'magnetCommentId' => (int) $magnetComment->magnetCommentId, - 'magnetCommentIdParent' => (int) $magnetComment->magnetCommentIdParent, - 'userId' => (int) $magnetComment->userId, - 'magnetId' => (int) $magnetComment->magnetId, - 'timeAdded' => (int) $magnetComment->timeAdded, - 'approved' => (bool) $magnetComment->approved, - 'value' => (string) $magnetComment->value - ]; - } - } - } - - // Magnet star request - if (!empty($push->magnetStarId) && API_EXPORT_MAGNET_STARS_ENABLED) - { - // Get magnet star info - if ($magnetStar = $db->getMagnetStar($push->magnetStarId)) - { - // Dump public data only - if (!empty($public['magnet'][$magnetStar->magnetId]) && - !empty($public['user'][$magnetStar->userId])) - { - $request['magnetStar'] = (object) - [ - 'magnetStarId' => (int) $magnetStar->magnetStarId, - 'userId' => (int) $magnetStar->userId, - 'magnetId' => (int) $magnetStar->magnetId, - 'value' => (bool) $magnetStar->value, - 'timeAdded' => (int) $magnetStar->timeAdded, - ]; - } - } - } - - // Magnet view request - if (!empty($push->magnetViewId) && API_EXPORT_MAGNET_VIEWS_ENABLED) - { - // Get magnet view info - if ($magnetView = $db->getMagnetView($push->magnetViewId)) - { - // Dump public data only - if (!empty($public['magnet'][$magnetView->magnetId]) && - !empty($public['user'][$magnetView->userId])) - { - $request['magnetView'] = (object) - [ - 'magnetViewId' => (int) $magnetView->magnetViewId, - 'userId' => (int) $magnetView->userId, - 'magnetId' => (int) $magnetView->magnetId, - 'timeAdded' => (int) $magnetView->timeAdded, - ]; - } - } - } - - // Check request - if (empty($request)) - { - // Amy request data match conditions, skip - - continue; - } - - // Send push data - foreach (json_decode( - file_get_contents(__DIR__ . '/../../config/nodes.json') - ) as $node) - { - // Manifest exists - if (empty($node->manifest)) - { - $debug['dump']['warning'][] = sprintf( - _('Manifest URL not provided: %s'), - $node - ); - - continue; - } - - // Skip non-condition addresses - $error = []; - - if (!Valid::url($node->manifest, $error)) - { - $debug['dump'][$node->manifest]['warning'][] = sprintf( - _('Manifest URL invalid: %s'), - print_r( - $error, - true - ) - ); - - continue; - } - - // Skip current host - $thisUrl = Yggverse\Parser\Url::parse(WEBSITE_URL); - $manifestUrl = Yggverse\Parser\Url::parse($node->manifest); - - if (empty($thisUrl->host->name) || - empty($manifestUrl->host->name) || - $manifestUrl->host->name == $thisUrl->host->name) // @TODO some mirrors could be available, improve condition - { - // No debug warnings in this case, continue next item - - continue; - } - - // Get node manifest - // @TODO cache to prevent extra-queries as usually this script running every minute - $curl = new Curl($node->manifest, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - $debug['dump'][$node->manifest]['warning'][] = sprintf( - _('Manifest unreachable with code: "%s"'), - $code - ); - - continue; - } - - if (!$manifest = $curl->getResponse()) - { - $debug['dump'][$node->manifest]['warning'][] = sprintf( - _('Manifest URL "%s" has broken response'), - $node->manifest - ); - - continue; - } - - // API channel not exists - if (empty($manifest->import)) - { - $debug['dump'][$node->manifest]['warning'][] = sprintf( - _('Manifest import URL not provided: %s'), - $node - ); - - continue; - } - - // Push API channel not exists - if (empty($manifest->import->push)) - { - $debug['dump'][$manifest->import->push]['warning'][] = sprintf( - _('Manifest import push URL not provided: %s'), - $node - ); - - continue; - } - - // Skip sending to non-condition addresses - $error = []; - - if (!Valid::url($manifest->import->push, $error)) - { - $debug['dump'][$manifest->import->push]['warning'][] = sprintf( - _('Manifest import push URL invalid: %s'), - print_r( - $error, - true - ) - ); - - continue; - } - - // Skip current host - $thisUrl = Yggverse\Parser\Url::parse(WEBSITE_URL); - $pushUrl = Yggverse\Parser\Url::parse($manifest->import->push); - - if (empty($thisUrl->host->name) || - empty($pushUrl->host->name) || - $pushUrl->host->name == $thisUrl->host->name) // @TODO some mirrors could be available, improve condition - { - // No debug warnings in this case, continue next item - - continue; - } - - // @TODO add recipient manifest conditions check to not disturb it API without needs - - // Send push request - $debug['dump'][$manifest->import->push]['request'][] = $request; - - $curl = new Curl( - $manifest->import->push, - API_USER_AGENT, - [ - 'data' => json_encode($request) - ] - ); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - $debug['dump'][$manifest->import->push]['warning'][] = sprintf( - _('Server returned code "%s"'), - $code - ); - - continue; - } - - if (!$response = $curl->getResponse()) - { - $debug['dump'][$manifest->import->push]['warning'][] = _('Could not receive server response'); - - continue; - } - - $debug['dump'][$manifest->import->push]['response'][] = $response; - } - - // Drop processed item from queue - unset($memoryApiExportPush[$id]); - } - - // Update memory pool - $memory->set('api.export.push', $memoryApiExportPush, 3600); -} - -// Export push disabled, free api.export.push pool -else -{ - $memory->delete('api.export.push'); -} - -// Debug output -$debug['time']['total'] = microtime(true) - $debug['time']['total']; - -$debug['memory']['total'] = memory_get_usage() - $debug['memory']['start']; -$debug['memory']['peaks'] = memory_get_peak_usage(); - -$debug['db']['total']['select'] = $db->getDebug()->query->select->total; -$debug['db']['total']['insert'] = $db->getDebug()->query->insert->total; -$debug['db']['total']['update'] = $db->getDebug()->query->update->total; -$debug['db']['total']['delete'] = $db->getDebug()->query->delete->total; - -print_r($debug); - -// Debug log -if (LOG_CRONTAB_EXPORT_PUSH_ENABLED) -{ - @mkdir(LOG_DIRECTORY, 0770, true); - - if ($handle = fopen(LOG_DIRECTORY . '/' . LOG_CRONTAB_EXPORT_PUSH_FILENAME, 'a+')) - { - fwrite($handle, print_r($debug, true)); - fclose($handle); - - chmod(LOG_DIRECTORY . '/' . LOG_CRONTAB_EXPORT_PUSH_FILENAME, 0770); - } -} \ No newline at end of file diff --git a/src/crontab/import/feed.php b/src/crontab/import/feed.php deleted file mode 100644 index 0cefd3b..0000000 --- a/src/crontab/import/feed.php +++ /dev/null @@ -1,1193 +0,0 @@ - [], - 'time' => - [ - 'ISO8601' => date('c'), - 'total' => microtime(true), - ], - 'http' => - [ - 'total' => 0, - ], - 'memory' => - [ - 'start' => memory_get_usage(), - 'total' => 0, - 'peaks' => 0 - ], - 'db' => - [ - 'total' => [ - 'select' => 0, - 'insert' => 0, - 'update' => 0, - 'delete' => 0, - ] - ], -]; - -// Begin import -try -{ - // Transaction begin - $db->beginTransaction(); - - foreach (json_decode( - file_get_contents(__DIR__ . '/../../config/nodes.json') - ) as $node) - { - // Manifest exists - if (empty($node->manifest)) - { - array_push( - $debug['dump'], - sprintf( - _('Manifest URL not provided for this node: %s'), - $node - ) - ); - - continue; - } - - // Skip non-condition addresses - $error = []; - - if (!Valid::url($node->manifest, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Manifest URL "%s" invalid: %s'), - $node->manifest, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Skip current host - $thisUrl = Yggverse\Parser\Url::parse(WEBSITE_URL); - $manifestUrl = Yggverse\Parser\Url::parse($node->manifest); - - if (empty($thisUrl->host->name) || - empty($manifestUrl->host->name) || - $manifestUrl->host->name == $thisUrl->host->name) // @TODO some mirrors could be available, improve condition - { - // No debug warnings in this case, continue next item - - continue; - } - - // Get node manifest - $curl = new Curl($node->manifest, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Manifest URL "%s" unreachable with code: "%s"'), - $node->manifest, - $code - ) - ); - - continue; - } - - if (!$manifest = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Manifest URL "%s" has broken response'), - $node->manifest - ) - ); - - continue; - } - - if (empty($manifest->version) || $manifest->version !== API_VERSION) - { - array_push( - $debug['dump'], - sprintf( - _('Manifest API not compatible with local version "%s"'), - API_VERSION - ) - ); - - continue; - } - - if (empty($manifest->export)) - { - array_push( - $debug['dump'], - sprintf( - _('Manifest URL "%s" has broken protocol'), - $node->manifest - ) - ); - - continue; - } - - // Users - if (API_IMPORT_USERS_ENABLED) - { - $error = []; - - if (!Valid::url($manifest->export->users, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Users feed URL "%s" invalid: %s'), - $manifest->export->users, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Call feed - $curl = new Curl($manifest->export->users, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Users feed URL "%s" unreachable with code: "%s"'), - $manifest->export->users, - $code - ) - ); - - continue; - } - - if (!$remoteUsers = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Users feed URL "%s" has broken response'), - $manifest->export->users - ) - ); - - continue; - } - - // Init alias registry for this host - $aliasUserId = []; - - foreach ((object) $remoteUsers as $remoteUser) - { - // Validate required fields - $error = []; - - if (!Valid::user($remoteUser, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Users feed URL "%s" has invalid protocol: "%s" error: "%s"'), - $manifest->export->users, - print_r( - $remoteUser, - true - ), - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Skip import on user approved required - if (API_IMPORT_USERS_APPROVED_ONLY && !$remoteUser->approved) - { - // No debug warnings in this case, continue next item - - continue; - } - - // Init session - else if (!$localUser = $db->getUser( - $db->initUserId( - $remoteUser->address, - USER_AUTO_APPROVE_ON_IMPORT_APPROVED ? $remoteUser->approved : USER_DEFAULT_APPROVED, - $remoteUser->timeAdded - ) - )) - { - array_push( - $debug['dump'], - sprintf( - _('Could not init user with address "%s" using feed URL "%s"'), - $remoteUser->address, - $manifest->export->users - ) - ); - - continue; - } - - // Update time added if newer - if ($localUser->timeAdded < $remoteUser->timeAdded) - { - $db->updateUserTimeAdded( - $localUser->userId, - $remoteUser->timeAdded - ); - } - - // Update user info if newer - if ($localUser->timeUpdated < $remoteUser->timeUpdated) - { - // Update time updated - $db->updateUserTimeUpdated( - $localUser->userId, - $remoteUser->timeUpdated - ); - - // Update approved for existing user - if (USER_AUTO_APPROVE_ON_IMPORT_APPROVED && $localUser->approved !== $remoteUser->approved && $remoteUser->approved) - { - $db->updateUserApproved( - $localUser->userId, - $remoteUser->approved, - $remoteUser->timeUpdated - ); - } - - // Set public as received remotely - if (!$localUser->public) - { - $db->updateUserPublic( - $localUser->userId, - true, - $remoteUser->timeUpdated - ); - } - } - - // Register userId alias - $aliasUserId[$remoteUser->userId] = $localUser->userId; - } - - // Magnets - if (API_IMPORT_MAGNETS_ENABLED) - { - $error = []; - - if (!Valid::url($manifest->export->magnets, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnets feed URL "%s" invalid: %s'), - $manifest->export->magnets, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Call feed - $curl = new Curl($manifest->export->magnets, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnets feed URL "%s" unreachable with code: "%s"'), - $manifest->export->magnets, - $code - ) - ); - - continue; - } - - if (!$remoteMagnets = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnets feed URL "%s" has broken response'), - $manifest->export->magnets - ) - ); - - continue; - } - - // Init alias registry for this host - $aliasMagnetId = []; - - foreach ((object) $remoteMagnets as $remoteMagnet) - { - // Validate required fields by protocol - $error = []; - - if (!Valid::magnet($remoteMagnet, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnets feed URL "%s" has invalid protocol: "%s" error: "%s"'), - $manifest->export->magnets, - print_r( - $remoteMagnet, - true - ), - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Aliases check - if (!isset($aliasUserId[$remoteMagnet->userId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote userId "%s" not found in URL "%s" %s'), - $remoteMagnet->userId, - $manifest->export->magnets, - print_r( - $remoteMagnet, - true - ), - ) - ); - - continue; - } - - // Skip import on magnet approved required - if (API_IMPORT_MAGNETS_APPROVED_ONLY && !$remoteMagnet->approved) - { - // No debug warnings in this case, continue next item - - continue; - } - - // Add new magnet if not exist by timestamp added for this user - if (!$localMagnet = $db->findMagnet($aliasUserId[$remoteMagnet->userId], $remoteMagnet->timeAdded)) - { - $localMagnet = $db->getMagnet( - $db->addMagnet( - $aliasUserId[$remoteMagnet->userId], - $remoteMagnet->xl, - $remoteMagnet->dn, - '', // @TODO linkSource used for debug only, will be deleted later - true, - $remoteMagnet->comments, - $remoteMagnet->sensitive, - MAGNET_AUTO_APPROVE_ON_IMPORT_APPROVED ? $remoteMagnet->approved : MAGNET_DEFAULT_APPROVED, - $remoteMagnet->timeAdded - ) - ); - } - - // Update time added if newer - if ($localMagnet->timeAdded < $remoteMagnet->timeAdded) - { - $db->updateMagnetTimeAdded( - $localMagnet->magnetId, - $remoteMagnet->timeAdded - ); - } - - // Update info if remote newer - if ($localMagnet->timeUpdated < $remoteMagnet->timeUpdated) - { - // Magnet fields - $db->updateMagnetXl($localMagnet->magnetId, $remoteMagnet->xl, $remoteMagnet->timeUpdated); - $db->updateMagnetDn($localMagnet->magnetId, $remoteMagnet->dn, $remoteMagnet->timeUpdated); - $db->updateMagnetTitle($localMagnet->magnetId, $remoteMagnet->title, $remoteMagnet->timeUpdated); - $db->updateMagnetPreview($localMagnet->magnetId, $remoteMagnet->preview, $remoteMagnet->timeUpdated); - $db->updateMagnetDescription($localMagnet->magnetId, $remoteMagnet->description, $remoteMagnet->timeUpdated); - $db->updateMagnetComments($localMagnet->magnetId, $remoteMagnet->comments, $remoteMagnet->timeUpdated); - $db->updateMagnetSensitive($localMagnet->magnetId, $remoteMagnet->sensitive, $remoteMagnet->timeUpdated); - - if (MAGNET_AUTO_APPROVE_ON_IMPORT_APPROVED && $localMagnet->approved !== $remoteMagnet->approved && $remoteMagnet->approved) - { - $db->updateMagnetApproved($localMagnet->magnetId, $remoteMagnet->approved, $remoteMagnet->timeUpdated); - } - - // xt - foreach ((array) $remoteMagnet->xt as $xt) - { - switch ($xt->version) - { - case 1: - - $exist = false; - - foreach ($db->findMagnetToInfoHashByMagnetId($localMagnet->magnetId) as $result) - { - if ($infoHash = $db->getInfoHash($result->infoHashId)) - { - if ($infoHash->version == 1) - { - $exist = true; - } - } - } - - if (!$exist) - { - $db->addMagnetToInfoHash( - $localMagnet->magnetId, - $db->initInfoHashId( - $xt->value, 1 - ) - ); - } - - break; - - case 2: - - $exist = false; - - foreach ($db->findMagnetToInfoHashByMagnetId($localMagnet->magnetId) as $result) - { - if ($infoHash = $db->getInfoHash($result->infoHashId)) - { - if ($infoHash->version == 2) - { - $exist = true; - } - } - } - - if (!$exist) - { - $db->addMagnetToInfoHash( - $localMagnet->magnetId, - $db->initInfoHashId( - $xt->value, 2 - ) - ); - } - - break; - } - } - - // kt - $db->deleteMagnetToKeywordTopicByMagnetId($localMagnet->magnetId); - - foreach ($remoteMagnet->kt as $kt) - { - $db->initMagnetToKeywordTopicId( - $localMagnet->magnetId, - $db->initKeywordTopicId(trim(mb_strtolower($kt))) // @TODO - ); - } - - // tr - $db->deleteMagnetToAddressTrackerByMagnetId($localMagnet->magnetId); - - foreach ($remoteMagnet->tr as $tr) - { - if ($url = Yggverse\Parser\Url::parse($tr)) - { - $db->initMagnetToAddressTrackerId( - $localMagnet->magnetId, - $db->initAddressTrackerId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - - // as - $db->deleteMagnetToAcceptableSourceByMagnetId($localMagnet->magnetId); - - foreach ($remoteMagnet->as as $as) - { - if ($url = Yggverse\Parser\Url::parse($as)) - { - $db->initMagnetToAcceptableSourceId( - $localMagnet->magnetId, - $db->initAcceptableSourceId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - - // xs - $db->deleteMagnetToExactSourceByMagnetId($localMagnet->magnetId); - - foreach ($remoteMagnet->xs as $xs) - { - if ($url = Yggverse\Parser\Url::parse($xs)) - { - $db->initMagnetToExactSourceId( - $localMagnet->magnetId, - $db->initExactSourceId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - } - - // Add magnet alias for this host - $aliasMagnetId[$remoteMagnet->magnetId] = $localMagnet->magnetId; - } - - // Magnet comments - if (API_IMPORT_MAGNET_COMMENTS_ENABLED) - { - $error = []; - - if (!Valid::url($manifest->export->magnetComments, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet comments feed URL "%s" invalid: %s'), - $manifest->export->magnetComments, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Call feed - $curl = new Curl($manifest->export->magnetComments, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet comments feed URL "%s" unreachable with code: "%s"'), - $manifest->export->magnetComments, - $code - ) - ); - - continue; - } - - if (!$remoteMagnetComments = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet comments feed URL "%s" has broken response'), - $manifest->export->magnetComments - ) - ); - - continue; - } - - foreach ((object) $remoteMagnetComments as $remoteMagnetComment) - { - // Validate - $error = []; - - if (!Valid::magnetComment($remoteMagnetComment, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet comments feed URL "%s" has invalid protocol: "%s" error: "%s"'), - $manifest->export->magnetComments, - print_r( - $remoteMagnetComment, - true - ), - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Aliases check - if (!isset($aliasUserId[$remoteMagnetComment->userId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote userId "%s" not found in URL "%s" %s'), - $remoteMagnetComment->userId, - $manifest->export->magnetComments, - print_r( - $remoteMagnetComment, - true - ), - ) - ); - - continue; - } - - if (!isset($aliasMagnetId[$remoteMagnetComment->magnetId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote magnetId "%s" not found in URL "%s" %s'), - $remoteMagnetComment->magnetId, - $manifest->export->magnetComments, - print_r( - $remoteMagnetComment, - true - ), - ) - ); - - continue; - } - - // Skip import on magnet comment approved required - if (API_IMPORT_MAGNET_COMMENTS_APPROVED_ONLY && !$remoteMagnetComment->approved) - { - // No debug warnings in this case, continue next item - - continue; - } - - // Add new magnet comment if not exist by timestamp added for this user - if (!$db->findMagnetComment($aliasMagnetId[$remoteMagnetComment->magnetId], - $aliasUserId[$remoteMagnetComment->userId], - $remoteMagnetComment->timeAdded)) - { - // Parent comment provided - if (is_int($remoteMagnetComment->magnetCommentIdParent)) - { - $localMagnetCommentIdParent = null; // @TODO feature not in use yet - } - - else - { - $localMagnetCommentIdParent = null; - } - - $db->addMagnetComment( - $aliasMagnetId[$remoteMagnetComment->magnetId], - $aliasUserId[$remoteMagnetComment->userId], - $localMagnetCommentIdParent, - $remoteMagnetComment->value, - $remoteMagnetComment->approved, - true, - $remoteMagnetComment->timeAdded - ); - } - } - } - - // Magnet downloads - if (API_IMPORT_MAGNET_DOWNLOADS_ENABLED) - { - // Skip non-condition addresses - $error = []; - - if (!Valid::url($manifest->export->magnetDownloads, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet downloads feed URL "%s" invalid: %s'), - $manifest->export->magnetDownloads, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Call feed - $curl = new Curl($manifest->export->magnetDownloads, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet downloads feed URL "%s" unreachable with code: "%s"'), - $manifest->export->magnetDownloads, - $code - ) - ); - - continue; - } - - if (!$remoteMagnetDownloads = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet downloads feed URL "%s" has broken response'), - $manifest->export->magnetDownloads - ) - ); - - continue; - } - - foreach ((object) $remoteMagnetDownloads as $remoteMagnetDownload) - { - // Validate - $error = []; - - if (!Valid::magnetDownload($remoteMagnetDownload, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet downloads feed URL "%s" has invalid protocol: "%s" error: "%s"'), - $manifest->export->magnetDownloads, - print_r( - $remoteMagnetDownload, - true - ), - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Aliases check - if (!isset($aliasUserId[$remoteMagnetDownload->userId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote userId "%s" not found in URL "%s" %s'), - $remoteMagnetDownload->userId, - $manifest->export->magnetDownloads, - print_r( - $remoteMagnetDownload, - true - ), - ) - ); - - continue; - } - - if (!isset($aliasMagnetId[$remoteMagnetDownload->magnetId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote magnetId "%s" not found in URL "%s" %s'), - $remoteMagnetDownload->magnetId, - $manifest->export->magnetDownloads, - print_r( - $remoteMagnetDownload, - true - ), - ) - ); - - continue; - } - - // Add new magnet download if not exist by timestamp added for this user - if (!$db->findMagnetDownload($aliasMagnetId[$remoteMagnetDownload->magnetId], - $aliasUserId[$remoteMagnetDownload->userId], - $remoteMagnetDownload->timeAdded)) - { - $db->addMagnetDownload( - $aliasMagnetId[$remoteMagnetDownload->magnetId], - $aliasUserId[$remoteMagnetDownload->userId], - $remoteMagnetDownload->timeAdded - ); - } - } - } - - // Magnet views - if (API_IMPORT_MAGNET_VIEWS_ENABLED) - { - $error = []; - - if (!Valid::url($manifest->export->magnetViews, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet views feed URL "%s" invalid: %s'), - $manifest->export->magnetViews, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Call feed - $curl = new Curl($manifest->export->magnetViews, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet views feed URL "%s" unreachable with code: "%s"'), - $manifest->export->magnetViews, - $code - ) - ); - - continue; - } - - if (!$remoteMagnetViews = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet views feed URL "%s" has broken response'), - $manifest->export->magnetViews - ) - ); - - continue; - } - - foreach ((object) $remoteMagnetViews as $remoteMagnetView) - { - // Validate - $error = []; - - if (!Valid::magnetView($remoteMagnetView, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet views feed URL "%s" has invalid protocol: "%s" error: "%s"'), - $manifest->export->magnetViews, - print_r( - $remoteMagnetView, - true - ), - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Aliases check - if (!isset($aliasUserId[$remoteMagnetView->userId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote userId "%s" not found in URL "%s" %s'), - $remoteMagnetView->userId, - $manifest->export->magnetViews, - print_r( - $remoteMagnetView, - true - ), - ) - ); - - continue; - } - - if (!isset($aliasMagnetId[$remoteMagnetView->magnetId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote magnetId "%s" not found in URL "%s" %s'), - $remoteMagnetView->magnetId, - $manifest->export->magnetViews, - print_r( - $remoteMagnetView, - true - ), - ) - ); - - continue; - } - - // Add new magnet view if not exist by timestamp added for this user - if (!$db->findMagnetView($aliasMagnetId[$remoteMagnetView->magnetId], - $aliasUserId[$remoteMagnetView->userId], - $remoteMagnetView->timeAdded)) - { - $db->addMagnetView( - $aliasMagnetId[$remoteMagnetView->magnetId], - $aliasUserId[$remoteMagnetView->userId], - $remoteMagnetView->timeAdded - ); - } - } - } - - // Magnet stars - if (API_IMPORT_MAGNET_STARS_ENABLED) - { - $error = []; - - if (!Valid::url($manifest->export->magnetStars, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet stars feed URL "%s" invalid: %s'), - $manifest->export->magnetStars, - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Call feed - $curl = new Curl($manifest->export->magnetStars, API_USER_AGENT); - - $debug['http']['total']++; - - if (200 != $code = $curl->getCode()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet stars feed URL "%s" unreachable with code: "%s"'), - $manifest->export->magnetStars, - $code - ) - ); - - continue; - } - - if (!$remoteMagnetStars = $curl->getResponse()) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet stars feed URL "%s" has broken response'), - $manifest->export->magnetStars - ) - ); - - continue; - } - - foreach ((object) $remoteMagnetStars as $remoteMagnetStar) - { - // Validate - $error = []; - - if (!Valid::magnetStar($remoteMagnetStar, $error)) - { - array_push( - $debug['dump'], - sprintf( - _('Magnet stars feed URL "%s" has invalid protocol: "%s" error: "%s"'), - $manifest->export->magnetStars, - print_r( - $remoteMagnetStar, - true - ), - print_r( - $error, - true - ) - ) - ); - - continue; - } - - // Aliases check - if (!isset($aliasUserId[$remoteMagnetStar->userId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote userId "%s" not found in URL "%s" %s'), - $remoteMagnetStar->userId, - $manifest->export->magnetStars, - print_r( - $remoteMagnetStar, - true - ), - ) - ); - - continue; - } - - if (!isset($aliasMagnetId[$remoteMagnetStar->magnetId])) - { - array_push( - $debug['dump'], - sprintf( - _('Local alias for remote magnetId "%s" not found in URL "%s" %s'), - $remoteMagnetStar->magnetId, - $manifest->export->magnetStars, - print_r( - $remoteMagnetStar, - true - ), - ) - ); - - continue; - } - - // Add new magnet star if not exist by timestamp added for this user - if (!$db->findMagnetStar($aliasMagnetId[$remoteMagnetStar->magnetId], - $aliasUserId[$remoteMagnetStar->userId], - $remoteMagnetStar->timeAdded)) - { - $db->addMagnetStar( - $aliasMagnetId[$remoteMagnetStar->magnetId], - $aliasUserId[$remoteMagnetStar->userId], - $remoteMagnetStar->value, - $remoteMagnetStar->timeAdded - ); - } - } - } - } - } - } - - $db->commit(); - -} catch (EXception $e) { - - $db->rollBack(); - - var_dump($e); -} - -// Debug output -$debug['time']['total'] = microtime(true) - $debug['time']['total']; - -$debug['memory']['total'] = memory_get_usage() - $debug['memory']['start']; -$debug['memory']['peaks'] = memory_get_peak_usage(); - -$debug['db']['total']['select'] = $db->getDebug()->query->select->total; -$debug['db']['total']['insert'] = $db->getDebug()->query->insert->total; -$debug['db']['total']['update'] = $db->getDebug()->query->update->total; -$debug['db']['total']['delete'] = $db->getDebug()->query->delete->total; - -print_r($debug); - -// Debug log -if (LOG_CRONTAB_IMPORT_FEED_ENABLED) -{ - @mkdir(LOG_DIRECTORY, 0770, true); - - if ($handle = fopen(LOG_DIRECTORY . '/' . LOG_CRONTAB_IMPORT_FEED_FILENAME, 'a+')) - { - fwrite($handle, print_r($debug, true)); - fclose($handle); - - chmod(LOG_DIRECTORY . '/' . LOG_CRONTAB_IMPORT_FEED_FILENAME, 0770); - } -} \ No newline at end of file diff --git a/src/crontab/scrape.php b/src/crontab/scrape.php deleted file mode 100644 index f974165..0000000 --- a/src/crontab/scrape.php +++ /dev/null @@ -1,158 +0,0 @@ - [ - 'ISO8601' => date('c'), - 'total' => microtime(true), - ], - 'http' => - [ - 'total' => 0, - ], - 'memory' => - [ - 'start' => memory_get_usage(), - 'total' => 0, - 'peaks' => 0 - ], -]; - -// Init Scraper -try { - - $scraper = new Scrapeer\Scraper(); - -} catch(Exception $e) { - - var_dump($e); - - exit; -} - -// Begin -try { - - $db->beginTransaction(); - - // Reset time offline by timeout - $db->resetMagnetToAddressTrackerTimeOfflineByTimeout( - CRAWLER_SCRAPE_TIME_OFFLINE_TIMEOUT - ); - - foreach ($db->getMagnetToAddressTrackerScrapeQueue(CRAWLER_SCRAPE_QUEUE_LIMIT) as $queue) - { - $hashes = []; - foreach ($db->findMagnetToInfoHashByMagnetId($queue->magnetId) as $result) - { - $hashes[] = $db->getInfoHash($result->infoHashId)->value; - } - - if ($addressTracker = $db->getAddressTracker($queue->addressTrackerId)) - { - // Build url - $scheme = $db->getScheme($addressTracker->schemeId); - $host = $db->getHost($addressTracker->hostId); - $port = $db->getPort($addressTracker->portId); - $uri = $db->getUri($addressTracker->uriId); - - $url = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, - $host->value, - $port->value, - $uri->value) : sprintf('%s://%s%s', $scheme->value, - $host->value, - $uri->value); - - foreach ($hashes as $hash) - { - if ($scrape = $scraper->scrape([$hash], [$url], null, 1)) - { - $db->updateMagnetToAddressTrackerTimeOffline( - $queue->magnetToAddressTrackerId, - null - ); - - if (isset($scrape[$hash]['seeders'])) - { - $db->updateMagnetToAddressTrackerSeeders( - $queue->magnetToAddressTrackerId, - (int) $scrape[$hash]['seeders'], - time() - ); - } - - if (isset($scrape[$hash]['completed'])) - { - $db->updateMagnetToAddressTrackerCompleted( - $queue->magnetToAddressTrackerId, - (int) $scrape[$hash]['completed'], - time() - ); - } - - if (isset($scrape[$hash]['leechers'])) - { - $db->updateMagnetToAddressTrackerLeechers( - $queue->magnetToAddressTrackerId, - (int) $scrape[$hash]['leechers'], - time() - ); - } - } - else - { - $db->updateMagnetToAddressTrackerTimeOffline( - $queue->magnetToAddressTrackerId, - time() - ); - } - } - } - } - - $db->commit(); - -} catch (EXception $e) { - - $db->rollback(); - - var_dump($e); -} - -// Debug output -$debug['time']['total'] = microtime(true) - $debug['time']['total']; - -$debug['memory']['total'] = memory_get_usage() - $debug['memory']['start']; -$debug['memory']['peaks'] = memory_get_peak_usage(); - -$debug['db']['total']['select'] = $db->getDebug()->query->select->total; -$debug['db']['total']['insert'] = $db->getDebug()->query->insert->total; -$debug['db']['total']['update'] = $db->getDebug()->query->update->total; -$debug['db']['total']['delete'] = $db->getDebug()->query->delete->total; - -print_r($debug); - -// Debug log -if (LOG_CRONTAB_SCRAPE_ENABLED) -{ - @mkdir(LOG_DIRECTORY, 0774, true); - - if ($handle = fopen(LOG_DIRECTORY . '/' . LOG_CRONTAB_SCRAPE_FILENAME, 'a+')) - { - fwrite($handle, print_r($debug, true)); - fclose($handle); - - chmod(LOG_DIRECTORY . '/' . LOG_CRONTAB_SCRAPE_FILENAME, 0774); - } -} \ No newline at end of file diff --git a/src/crontab/sitemap.php b/src/crontab/sitemap.php deleted file mode 100644 index 1c65e87..0000000 --- a/src/crontab/sitemap.php +++ /dev/null @@ -1,86 +0,0 @@ - [ - 'ISO8601' => date('c'), - 'total' => microtime(true), - ], - 'http' => - [ - 'total' => 0, - ], - 'memory' => - [ - 'start' => memory_get_usage(), - 'total' => 0, - 'peaks' => 0 - ], -]; - -// Begin -try { - - // Delete cache - @unlink(__DIR__ . '/../public/sitemap.xml'); - - if ($handle = fopen(__DIR__ . '/../public/sitemap.xml', 'w+')) - { - - fwrite($handle, ''); - fwrite($handle, ''); - - foreach ($db->getMagnets() as $magnet) - { - if ($magnet->public && $magnet->approved) - { - fwrite($handle, sprintf('%s/magnet.php?magnetId=%s', WEBSITE_URL, $magnet->magnetId)); - } - } - - fwrite($handle, ''); - fclose($handle); - } - -} catch (EXception $e) { - - var_dump($e); -} - -// Debug output -$debug['time']['total'] = microtime(true) - $debug['time']['total']; - -$debug['memory']['total'] = memory_get_usage() - $debug['memory']['start']; -$debug['memory']['peaks'] = memory_get_peak_usage(); - -$debug['db']['total']['select'] = $db->getDebug()->query->select->total; -$debug['db']['total']['insert'] = $db->getDebug()->query->insert->total; -$debug['db']['total']['update'] = $db->getDebug()->query->update->total; -$debug['db']['total']['delete'] = $db->getDebug()->query->delete->total; - -print_r($debug); - -// Debug log -if (LOG_CRONTAB_SITEMAP_ENABLED) -{ - @mkdir(LOG_DIRECTORY, 0774, true); - - if ($handle = fopen(LOG_DIRECTORY . '/' . LOG_CRONTAB_SITEMAP_FILENAME, 'a+')) - { - fwrite($handle, print_r($debug, true)); - fclose($handle); - - chmod(LOG_DIRECTORY . '/' . LOG_CRONTAB_SITEMAP_FILENAME, 0774); - } -} \ No newline at end of file diff --git a/src/library/curl.php b/src/library/curl.php deleted file mode 100644 index 24c4021..0000000 --- a/src/library/curl.php +++ /dev/null @@ -1,97 +0,0 @@ -_connection = curl_init($url); - - if ($userAgent) - { - curl_setopt($this->_connection, CURLOPT_USERAGENT, $userAgent); - } - - if (!empty($post)) - { - curl_setopt($this->_connection, CURLOPT_POST, true); - curl_setopt($this->_connection, CURLOPT_POSTFIELDS, http_build_query($post)); - } - - if ($header) { - curl_setopt($this->_connection, CURLOPT_HEADER, true); - } - - if ($followLocation) { - curl_setopt($this->_connection, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($this->_connection, CURLOPT_MAXREDIRS, $maxRedirects); - } - - curl_setopt($this->_connection, CURLOPT_FRESH_CONNECT, true); - curl_setopt($this->_connection, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->_connection, CURLOPT_CONNECTTIMEOUT, $connectTimeout); - curl_setopt($this->_connection, CURLOPT_TIMEOUT, $connectTimeout); - curl_setopt($this->_connection, CURLOPT_SSL_VERIFYHOST, $sslVerifyHost); - curl_setopt($this->_connection, CURLOPT_SSL_VERIFYPEER, $sslVerifyPeer); - - $this->_response = curl_exec($this->_connection); - } - - public function __destruct() - { - curl_close($this->_connection); - } - - public function getError() - { - if (curl_errno($this->_connection)) - { - return curl_errno($this->_connection); - } - - else - { - return false; - } - } - - public function getCode() - { - return curl_getinfo($this->_connection, CURLINFO_HTTP_CODE); - } - - public function getContentType() - { - return curl_getinfo($this->_connection, CURLINFO_CONTENT_TYPE); - } - - public function getSizeDownload() - { - return curl_getinfo($this->_connection, CURLINFO_SIZE_DOWNLOAD); - } - - public function getSizeRequest() - { - return curl_getinfo($this->_connection, CURLINFO_REQUEST_SIZE); - } - - public function getTotalTime() - { - return curl_getinfo($this->_connection, CURLINFO_TOTAL_TIME_T); - } - - public function getResponse(bool $json = true) - { - return $json ? json_decode($this->_response) : $this->_response; - } -} \ No newline at end of file diff --git a/src/library/environment.php b/src/library/environment.php deleted file mode 100644 index b85516d..0000000 --- a/src/library/environment.php +++ /dev/null @@ -1,27 +0,0 @@ -1) or string of infohash(es). - * @param array|string $trackers List (>1) or string of tracker(s). - * @param int|null $max_trackers Optional. Maximum number of trackers to be scraped, Default all. - * @param int $timeout Optional. Maximum time for each tracker scrape in seconds, Default 2. - * @param bool $announce Optional. Use announce instead of scrape, Default false. - * @return array List of results. - */ - public function scrape( $hashes, $trackers, $max_trackers = null, $timeout = 2, $announce = false ) { - $final_result = array(); - - if ( empty( $trackers ) ) { - $this->errors[] = 'No tracker specified, aborting.'; - return $final_result; - } else if ( ! is_array( $trackers ) ) { - $trackers = array( $trackers ); - } - - if ( is_int( $timeout ) ) { - $this->timeout = $timeout; - } else { - $this->timeout = 2; - $this->errors[] = 'Timeout must be an integer. Using default value.'; - } - - try { - $this->infohashes = $this->normalize_infohashes( $hashes ); - } catch ( \RangeException $e ) { - $this->errors[] = $e->getMessage(); - return $final_result; - } - - $max_iterations = is_int( $max_trackers ) ? $max_trackers : count( $trackers ); - foreach ( $trackers as $index => $tracker ) { - if ( ! empty( $this->infohashes ) && $index < $max_iterations ) { - $info = parse_url( $tracker ); - $protocol = $info['scheme']; - $host = $info['host']; - if ( empty( $protocol ) || empty( $host ) ) { - $this->errors[] = 'Skipping invalid tracker (' . $tracker . ').'; - continue; - } - - $port = isset( $info['port'] ) ? $info['port'] : null; - $path = isset( $info['path'] ) ? $info['path'] : null; - $passkey = $this->get_passkey( $path ); - $result = $this->try_scrape( $protocol, $host, $port, $passkey, $announce ); - $final_result = array_merge( $final_result, $result ); - continue; - } - break; - } - return $final_result; - } - - /** - * Normalizes the given hashes - * - * @throws \RangeException If amount of valid infohashes > 64 or < 1. - * - * @param array $infohashes List of infohash(es). - * @return array Normalized infohash(es). - */ - private function normalize_infohashes( $infohashes ) { - if ( ! is_array( $infohashes ) ) { - $infohashes = array( $infohashes ); - } - - foreach ( $infohashes as $index => $infohash ) { - if ( ! preg_match( '/^[a-f0-9]{40}$/i', $infohash ) ) { - $this->errors[] = 'Invalid infohash skipped (' . $infohash . ').'; - unset( $infohashes[ $index ] ); - } - } - - $total_infohashes = count( $infohashes ); - if ( $total_infohashes > 64 || $total_infohashes < 1 ) { - throw new \RangeException( 'Invalid amount of valid infohashes (' . $total_infohashes . ').' ); - } - - $infohashes = array_values( $infohashes ); - - return $infohashes; - } - - /** - * Returns the passkey found in the scrape request. - * - * @param string $path Path from the scrape request. - * @return string Passkey or empty string. - */ - private function get_passkey( $path ) { - if ( ! is_null( $path ) && preg_match( '/[a-z0-9]{32}/i', $path, $matches ) ) { - return '/' . $matches[0]; - } - - return ''; - } - - /** - * Tries to scrape with a single tracker. - * - * @throws \Exception In case of unsupported protocol. - * - * @param string $protocol Protocol of the tracker. - * @param string $host Domain or address of the tracker. - * @param int $port Optional. Port number of the tracker. - * @param string $passkey Optional. Passkey provided in the scrape request. - * @param bool $announce Optional. Use announce instead of scrape, Default false. - * @return array List of results. - */ - private function try_scrape( $protocol, $host, $port, $passkey, $announce ) { - $infohashes = $this->infohashes; - $this->infohashes = array(); - $results = array(); - try { - switch ( $protocol ) { - case 'udp': - $port = isset( $port ) ? $port : 80; - $results = $this->scrape_udp( $infohashes, $host, $port, $announce ); - break; - case 'http': - $port = isset( $port ) ? $port : 80; - $results = $this->scrape_http( $infohashes, $protocol, $host, $port, $passkey, $announce ); - break; - case 'https': - $port = isset( $port ) ? $port : 443; - $results = $this->scrape_http( $infohashes, $protocol, $host, $port, $passkey, $announce ); - break; - default: - throw new \Exception( 'Unsupported protocol (' . $protocol . '://' . $host . ').' ); - } - } catch ( \Exception $e ) { - $this->infohashes = $infohashes; - $this->errors[] = $e->getMessage(); - } - return $results; - } - - /** - * Initiates the HTTP(S) scraping - * - * @param array|string $infohashes List (>1) or string of infohash(es). - * @param string $protocol Protocol to use for the scraping. - * @param string $host Domain or IP address of the tracker. - * @param int $port Optional. Port number of the tracker, Default 80 (HTTP) or 443 (HTTPS). - * @param string $passkey Optional. Passkey provided in the scrape request. - * @param bool $announce Optional. Use announce instead of scrape, Default false. - * @return array List of results. - */ - private function scrape_http( $infohashes, $protocol, $host, $port, $passkey, $announce ) { - if ( true === $announce ) { - $response = $this->http_announce( $infohashes, $protocol, $host, $port, $passkey ); - } else { - $query = $this->http_query( $infohashes, $protocol, $host, $port, $passkey ); - $response = $this->http_request( $query, $host, $port ); - } - $results = $this->http_data( $response, $infohashes, $host ); - - return $results; - } - - /** - * Builds the HTTP(S) query - * - * @param array|string $infohashes List (>1) or string of infohash(es). - * @param string $protocol Protocol to use for the scraping. - * @param string $host Domain or IP address of the tracker. - * @param int $port Port number of the tracker, Default 80 (HTTP) or 443 (HTTPS). - * @param string $passkey Optional. Passkey provided in the scrape request. - * @return string Request query. - */ - private function http_query( $infohashes, $protocol, $host, $port, $passkey ) { - $tracker_url = $protocol . '://' . $host . ':' . $port . $passkey; - $scrape_query = ''; - - foreach ( $infohashes as $index => $infohash ) { - if ( $index > 0 ) { - $scrape_query .= '&info_hash=' . urlencode( pack( 'H*', $infohash ) ); - } else { - $scrape_query .= '/scrape?info_hash=' . urlencode( pack( 'H*', $infohash ) ); - } - } - $request_query = $tracker_url . $scrape_query; - - return $request_query; - } - - /** - * Executes the query and returns the result - * - * @throws \Exception If the connection can't be established. - * @throws \Exception If the response isn't valid. - * - * @param string $query The query that will be executed. - * @param string $host Domain or IP address of the tracker. - * @param int $port Port number of the tracker, Default 80 (HTTP) or 443 (HTTPS). - * @return string Request response. - */ - private function http_request( $query, $host, $port ) { - $context = stream_context_create( array( - 'http' => array( - 'timeout' => $this->timeout, - ), - )); - - if ( false === ( $response = @file_get_contents( $query, false, $context ) ) ) { - throw new \Exception( 'Invalid scrape connection (' . $host . ':' . $port . ').' ); - } - - if ( substr( $response, 0, 12 ) !== 'd5:filesd20:' ) { - throw new \Exception( 'Invalid scrape response (' . $host . ':' . $port . ').' ); - } - - return $response; - } - - /** - * Builds the query, sends the announce request and returns the data - * - * @throws \Exception If the connection can't be established. - * - * @param array|string $infohashes List (>1) or string of infohash(es). - * @param string $protocol Protocol to use for the scraping. - * @param string $host Domain or IP address of the tracker. - * @param int $port Port number of the tracker, Default 80 (HTTP) or 443 (HTTPS). - * @param string $passkey Optional. Passkey provided in the scrape request. - * @return string Request response. - */ - private function http_announce( $infohashes, $protocol, $host, $port, $passkey ) { - $tracker_url = $protocol . '://' . $host . ':' . $port . $passkey; - $context = stream_context_create( array( - 'http' => array( - 'timeout' => $this->timeout, - ), - )); - - $response_data = ''; - foreach ( $infohashes as $infohash ) { - $query = $tracker_url . '/announce?info_hash=' . urlencode( pack( 'H*', $infohash ) ); - if ( false === ( $response = @file_get_contents( $query, false, $context ) ) ) { - throw new \Exception( 'Invalid announce connection (' . $host . ':' . $port . ').' ); - } - - if ( substr( $response, 0, 12 ) !== 'd8:completei' || - substr( $response, 0, 46 ) === 'd8:completei0e10:downloadedi0e10:incompletei1e' ) { - continue; - } - - $ben_hash = '20:' . pack( 'H*', $infohash ) . 'd'; - $response_data .= $ben_hash . $response; - } - - return $response_data; - } - - /** - * Parses the response and returns the data - * - * @param string $response The response that will be parsed. - * @param array $infohashes List of infohash(es). - * @param string $host Domain or IP address of the tracker. - * @return array Parsed data. - */ - private function http_data( $response, $infohashes, $host ) { - $torrents_data = array(); - - foreach ( $infohashes as $infohash ) { - $ben_hash = '20:' . pack( 'H*', $infohash ) . 'd'; - $start_pos = strpos( $response, $ben_hash ); - if ( false !== $start_pos ) { - $start = $start_pos + 24; - $head = substr( $response, $start ); - $end = strpos( $head, 'ee' ) + 1; - $data = substr( $response, $start, $end ); - - $seeders = '8:completei'; - $torrent_info['seeders'] = $this->get_information( $data, $seeders, 'e' ); - - $completed = '10:downloadedi'; - $torrent_info['completed'] = $this->get_information( $data, $completed, 'e' ); - - $leechers = '10:incompletei'; - $torrent_info['leechers'] = $this->get_information( $data, $leechers, 'e' ); - - $torrents_data[ $infohash ] = $torrent_info; - } else { - $this->collect_infohash( $infohash ); - $this->errors[] = 'Invalid infohash (' . $infohash . ') for tracker: ' . $host . '.'; - } - } - - return $torrents_data; - } - - /** - * Parses a string and returns the data between $start and $end. - * - * @param string $data The data that will be parsed. - * @param string $start Beginning part of the data. - * @param string $end Ending part of the data. - * @return int Parsed information or 0. - */ - private function get_information( $data, $start, $end ) { - $start_pos = strpos( $data, $start ); - if ( false !== $start_pos ) { - $start = $start_pos + strlen( $start ); - $head = substr( $data, $start ); - $end = strpos( $head, $end ); - $information = substr( $data, $start, $end ); - - return (int) $information; - } - - return 0; - } - - /** - * Initiates the UDP scraping - * - * @param array|string $infohashes List (>1) or string of infohash(es). - * @param string $host Domain or IP address of the tracker. - * @param int $port Optional. Port number of the tracker, Default 80. - * @param bool $announce Optional. Use announce instead of scrape, Default false. - * @return array List of results. - */ - private function scrape_udp( $infohashes, $host, $port, $announce ) { - list( $socket, $transaction_id, $connection_id ) = $this->prepare_udp( $host, $port ); - - if ( true === $announce ) { - $response = $this->udp_announce( $socket, $infohashes, $connection_id ); - $keys = 'Nleechers/Nseeders'; - $start = 12; - $end = 16; - $offset = 20; - } else { - $response = $this->udp_scrape( $socket, $infohashes, $connection_id, $transaction_id, $host, $port ); - $keys = 'Nseeders/Ncompleted/Nleechers'; - $start = 8; - $end = $offset = 12; - } - $results = $this->udp_scrape_data( $response, $infohashes, $host, $keys, $start, $end, $offset ); - - return $results; - } - - /** - * Prepares the UDP connection - * - * @param string $host Domain or IP address of the tracker. - * @param int $port Optional. Port number of the tracker, Default 80. - * @return array Created socket, transaction ID and connection ID. - */ - private function prepare_udp( $host, $port ) { - $socket = $this->udp_create_connection( $host, $port ); - $transaction_id = $this->udp_connection_request( $socket ); - $connection_id = $this->udp_connection_response( $socket, $transaction_id, $host, $port ); - - return array( $socket, $transaction_id, $connection_id ); - } - - /** - * Creates the UDP socket and establishes the connection - * - * @throws \Exception If the socket couldn't be created or connected to. - * - * @param string $host Domain or IP address of the tracker. - * @param int $port Port number of the tracker, Default 80. - * @return resource $socket Created and connected socket. - */ - private function udp_create_connection( $host, $port ) { - if ( false === ( $socket = @socket_create( AF_INET, SOCK_DGRAM, SOL_UDP ) ) ) { - throw new \Exception( "Couldn't create socket." ); - } - - $timeout = $this->timeout; - socket_set_option( $socket, SOL_SOCKET, SO_RCVTIMEO, array( 'sec' => $timeout, 'usec' => 0 ) ); - socket_set_option( $socket, SOL_SOCKET, SO_SNDTIMEO, array( 'sec' => $timeout, 'usec' => 0 ) ); - if ( false === @socket_connect( $socket, $host, $port ) ) { - throw new \Exception( "Couldn't connect to socket." ); - } - - return $socket; - } - - /** - * Writes to the connected socket and returns the transaction ID - * - * @throws \Exception If the socket couldn't be written to. - * - * @param resource $socket The socket resource. - * @return int The transaction ID. - */ - private function udp_connection_request( $socket ) { - $connection_id = "\x00\x00\x04\x17\x27\x10\x19\x80"; - $action = pack( 'N', 0 ); - $transaction_id = mt_rand( 0, 2147483647 ); - $buffer = $connection_id . $action . pack( 'N', $transaction_id ); - if ( false === @socket_write( $socket, $buffer, strlen( $buffer ) ) ) { - socket_close( $socket ); - throw new \Exception( "Couldn't write to socket." ); - } - - return $transaction_id; - } - - /** - * Reads the connection response and returns the connection ID - * - * @throws \Exception If anything fails with the scraping. - * - * @param resource $socket The socket resource. - * @param int $transaction_id The transaction ID. - * @param string $host Domain or IP address of the tracker. - * @param int $port Port number of the tracker, Default 80. - * @return string The connection ID. - */ - private function udp_connection_response( $socket, $transaction_id, $host, $port ) { - if ( false === ( $response = @socket_read( $socket, 16 ) ) ) { - socket_close( $socket ); - throw new \Exception( 'Invalid scrape connection! (' . $host . ':' . $port . ').' ); - } - - if ( strlen( $response ) < 16 ) { - socket_close( $socket ); - throw new \Exception( 'Invalid scrape response (' . $host . ':' . $port . ').' ); - } - - $result = unpack( 'Naction/Ntransaction_id', $response ); - if ( 0 !== $result['action'] || $result['transaction_id'] !== $transaction_id ) { - socket_close( $socket ); - throw new \Exception( 'Invalid scrape result (' . $host . ':' . $port . ').' ); - } - - $connection_id = substr( $response, 8, 8 ); - - return $connection_id; - } - - /** - * Reads the socket response and returns the torrent data - * - * @throws \Exception If anything fails while reading the response. - * - * @param resource $socket The socket resource. - * @param array $hashes List (>1) or string of infohash(es). - * @param string $connection_id The connection ID. - * @param int $transaction_id The transaction ID. - * @param string $host Domain or IP address of the tracker. - * @param int $port Port number of the tracker, Default 80. - * @return string Response data. - */ - private function udp_scrape( $socket, $hashes, $connection_id, $transaction_id, $host, $port ) { - $this->udp_scrape_request( $socket, $hashes, $connection_id, $transaction_id ); - - $read_length = 8 + ( 12 * count( $hashes ) ); - if ( false === ( $response = @socket_read( $socket, $read_length ) ) ) { - socket_close( $socket ); - throw new \Exception( 'Invalid scrape connection (' . $host . ':' . $port . ').' ); - } - socket_close( $socket ); - - if ( strlen( $response ) < $read_length ) { - throw new \Exception( 'Invalid scrape response (' . $host . ':' . $port . ').' ); - } - - $result = unpack( 'Naction/Ntransaction_id', $response ); - if ( 2 !== $result['action'] || $result['transaction_id'] !== $transaction_id ) { - throw new \Exception( 'Invalid scrape result (' . $host . ':' . $port . ').' ); - } - - return $response; - } - - /** - * Writes to the connected socket - * - * @throws \Exception If the socket couldn't be written to. - * - * @param resource $socket The socket resource. - * @param array $hashes List (>1) or string of infohash(es). - * @param string $connection_id The connection ID. - * @param int $transaction_id The transaction ID. - */ - private function udp_scrape_request( $socket, $hashes, $connection_id, $transaction_id ) { - $action = pack( 'N', 2 ); - - $infohashes = ''; - foreach ( $hashes as $infohash ) { - $infohashes .= pack( 'H*', $infohash ); - } - - $buffer = $connection_id . $action . pack( 'N', $transaction_id ) . $infohashes; - if ( false === @socket_write( $socket, $buffer, strlen( $buffer ) ) ) { - socket_close( $socket ); - throw new \Exception( "Couldn't write to socket." ); - } - } - - /** - * Writes the announce to the connected socket - * - * @throws \Exception If the socket couldn't be written to. - * - * @param resource $socket The socket resource. - * @param array $hashes List (>1) or string of infohash(es). - * @param string $connection_id The connection ID. - * @return string Torrent(s) data. - */ - private function udp_announce( $socket, $hashes, $connection_id ) { - $action = pack( 'N', 1 ); - $downloaded = $left = $uploaded = "\x30\x30\x30\x30\x30\x30\x30\x30"; - $peer_id = $this->random_peer_id(); - $event = pack( 'N', 3 ); - $ip_addr = pack( 'N', 0 ); - $key = pack( 'N', mt_rand( 0, 2147483647 ) ); - $num_want = -1; - $ann_port = pack( 'N', mt_rand( 0, 255 ) ); - - $response_data = ''; - foreach ( $hashes as $infohash ) { - $transaction_id = mt_rand( 0, 2147483647 ); - $buffer = $connection_id . $action . pack( 'N', $transaction_id ) . pack( 'H*', $infohash ) . - $peer_id . $downloaded . $left . $uploaded . $event . $ip_addr . $key . $num_want . $ann_port; - - if ( false === @socket_write( $socket, $buffer, strlen( $buffer ) ) ) { - socket_close( $socket ); - throw new \Exception( "Couldn't write announce to socket." ); - } - - $response = $this->udp_verify_announce( $socket, $transaction_id ); - if ( false === $response ) { - continue; - } - - $response_data .= $response; - } - socket_close( $socket ); - - return $response_data; - } - - /** - * Generates a random peer ID - * - * @return string Generated peer ID. - */ - private function random_peer_id() { - $identifier = '-SP0054-'; - $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - $peer_id = $identifier . substr( str_shuffle( $chars ), 0, 12 ); - - return $peer_id; - } - - /** - * Verifies the correctness of the announce response - * - * @param resource $socket The socket resource. - * @param int $transaction_id The transaction ID. - * @return string Response data. - */ - private function udp_verify_announce( $socket, $transaction_id ) { - if ( false === ( $response = @socket_read( $socket, 20 ) ) ) { - return false; - } - - if ( strlen( $response ) < 20 ) { - return false; - } - - $result = unpack( 'Naction/Ntransaction_id', $response ); - if ( 1 !== $result['action'] || $result['transaction_id'] !== $transaction_id ) { - return false; - } - - return $response; - } - - /** - * Reads the socket response and returns the torrent data - * - * @param string $response Data from the request response. - * @param array $hashes List (>1) or string of infohash(es). - * @param string $host Domain or IP address of the tracker. - * @param string $keys Keys for the unpacked information. - * @param int $start Start of the content we want to unpack. - * @param int $end End of the content we want to unpack. - * @param int $offset Offset to the next content part. - * @return array Scraped torrent data. - */ - private function udp_scrape_data( $response, $hashes, $host, $keys, $start, $end, $offset ) { - $torrents_data = array(); - - foreach ( $hashes as $infohash ) { - $byte_string = substr( $response, $start, $end ); - $data = unpack( 'N', $byte_string ); - $content = $data[1]; - if ( ! empty( $content ) ) { - $results = unpack( $keys, $byte_string ); - $torrents_data[ $infohash ] = $results; - } else { - $this->collect_infohash( $infohash ); - $this->errors[] = 'Invalid infohash (' . $infohash . ') for tracker: ' . $host . '.'; - } - $start += $offset; - } - - return $torrents_data; - } - - /** - * Collects info-hashes that couldn't be scraped. - * - * @param string $infohash Infohash that wasn't scraped. - */ - private function collect_infohash( $infohash ) { - $this->infohashes[] = $infohash; - } - - /** - * Checks if there are any errors - * - * @return bool True or false, depending if errors are present or not. - */ - public function has_errors() { - return ! empty( $this->errors ); - } - - /** - * Returns all the errors that were logged - * - * @return array All the logged errors. - */ - public function get_errors() { - return $this->errors; - } -} diff --git a/src/library/time.php b/src/library/time.php deleted file mode 100644 index 9199d3c..0000000 --- a/src/library/time.php +++ /dev/null @@ -1,45 +0,0 @@ - _('year'), - 30 * 24 * 60 * 60 => _('month'), - 24 * 60 * 60 => _('day'), - 60 * 60 => _('hour'), - 60 => _('minute'), - 1 => _('second') - ]; - - $plural = [ - _('year') => _('years'), - _('month') => _('months'), - _('day') => _('days'), - _('hour') => _('hours'), - _('minute') => _('minutes'), - _('second') => _('seconds') - ]; - - foreach ($values as $key => $value) - { - $result = $diff / $key; - - if ($result >= 1) - { - $round = round($result); - - return sprintf('%s %s ago', $round, $round > 1 ? $plural[$value] : $value); - } - } - } -} diff --git a/src/public/api/push.php b/src/public/api/push.php deleted file mode 100644 index ad63354..0000000 --- a/src/public/api/push.php +++ /dev/null @@ -1,938 +0,0 @@ - [ - 'ISO8601' => date('c'), - 'total' => microtime(true), - ], - 'memory' => - [ - 'start' => memory_get_usage(), - 'total' => 0, - 'peaks' => 0 - ], - 'exception' => [] -]; - -// Define response -$response = -[ - 'status' => false, - 'message' => _('Internal server error'), - 'data' => [ - 'user' => [], - 'magnet' => [], - 'magnetDownload' => [], - 'magnetComment' => [], - 'magnetView' => [], - 'magnetStar' => [], - ] -]; - -// Init connections whitelist -$connectionWhiteList = []; - -foreach (json_decode(file_get_contents(__DIR__ . '/../../config/nodes.json')) as $node) -{ - // Skip non-condition addresses - if (!Valid::url($node->manifest)) - { - continue; - } - - // Skip current host - $thisUrl = Yggverse\Parser\Url::parse(WEBSITE_URL); - $manifestUrl = Yggverse\Parser\Url::parse($node->manifest); - - if (empty($thisUrl->host->name) || - empty($manifestUrl->host->name) || - $manifestUrl->host->name == $thisUrl->host->name) // @TODO some mirrors could be available on same host sub-folders, improve condition - { - continue; - } - - $connectionWhiteList[] = str_replace(['[',']'], false, $manifestUrl->host->name); -} - -// API import enabled -$error = []; - -if (!API_IMPORT_ENABLED) -{ - $response = - [ - 'status' => false, - 'message' => _('Import API disabled') - ]; -} - -// Push API import enabled -else if (!API_IMPORT_PUSH_ENABLED) -{ - $response = - [ - 'status' => false, - 'message' => _('Push API import disabled') - ]; -} - -// Yggdrasil connections only -else if (!Valid::host($_SERVER['REMOTE_ADDR'], $error)) -{ - $response = - [ - 'status' => false, - 'message' => $error - ]; -} - -// Init session -else if (!in_array($_SERVER['REMOTE_ADDR'], $connectionWhiteList)) -{ - $response = - [ - 'status' => false, - 'message' => sprintf( - _('Push API access denied for host "%s"'), - $_SERVER['REMOTE_ADDR'] - ) - ]; -} - -// Init session -else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) -{ - $response = - [ - 'status' => false, - 'message' => _('Could not init user session for this connection') - ]; -} - -// Validate required fields -else if (empty($_POST['data'])) -{ - $response = - [ - 'status' => false, - 'message' => _('Request protocol invalid') - ]; -} - -// Validate required fields -else if (false === $data = json_decode($_POST['data'])) -{ - $response = - [ - 'status' => false, - 'message' => _('Could not decode data requested') - ]; -} - -// Import begin -else -{ - $response = - [ - 'status' => true, - 'message' => sprintf( - _('Connection for "%s" established'), - $_SERVER['REMOTE_ADDR'] - ) - ]; - - // Init alias registry - $aliasUserId = []; - $aliasMagnetId = []; - - try { - - // Transaction begin - $db->beginTransaction(); - - // Process request - foreach ((object) $data as $field => $remote) - { - // Process alias fields - switch ($field) - { - case 'user': - - if (!API_IMPORT_USERS_ENABLED) - { - $response['user'][] = [ - 'status' => false, - 'message' => _('Users import disabled on this node. Related content skipped.') - ]; - - continue 2; - } - - // Validate remote fields - $error = []; - - if (!Valid::user($remote, $error)) - { - $response['user'][] = [ - 'status' => false, - 'message' => sprintf( - _('User data mismatch protocol with error: %s'), - print_r($error, true) - ), - ]; - - continue 2; - } - - // Skip import on user approved required - if (API_IMPORT_USERS_APPROVED_ONLY && !$remote->approved) - { - $response['user'][] = [ - 'status' => false, - 'message' => _('Node accepting approved users only') - ]; - - continue 2; - } - - // Init local user by remote address - if (!$local = $db->getUser($db->initUserId($remote->address, - USER_AUTO_APPROVE_ON_IMPORT_APPROVED ? $remote->approved : USER_DEFAULT_APPROVED, - $remote->timeAdded))) - { - $response['user'][] = [ - 'status' => false, - 'message' => _('Could not init user profile') - ]; - - continue 2; - } - - else - { - $response['user'][] = [ - 'status' => true, - 'message' => sprintf( - _('User profile successfully associated with ID "%s"'), - $local->userId - ) - ]; - } - - // Register user alias - $aliasUserId[$remote->userId] = $local->userId; - - // Update time added if newer - if ($local->timeAdded < $remote->timeAdded) - { - $db->updateUserTimeAdded( - $local->userId, - $remote->timeAdded - ); - - $response['user'][] = [ - 'status' => true, - 'message' => sprintf( - _('Field "timeAdded" changed to newer value for user ID "%s"'), - $local->userId - ) - ]; - } - - // Update user info if newer - if ($local->timeUpdated < $remote->timeUpdated) - { - // Update time updated - $db->updateUserTimeUpdated( - $local->userId, - $remote->timeUpdated - ); - - $response['user'][] = [ - 'status' => true, - 'message' => sprintf( - _('Field "timeUpdated" changed to newer value for user ID "%s"'), - $local->userId - ) - ]; - - // Update approved for existing user - if (USER_AUTO_APPROVE_ON_IMPORT_APPROVED && $local->approved !== $remote->approved && $remote->approved) - { - $db->updateUserApproved( - $local->userId, - $remote->approved, - $remote->timeUpdated - ); - - $response['user'][] = [ - 'status' => true, - 'message' => sprintf( - _('Field "approved" changed to newer value for user ID "%s"'), - $local->userId - ) - ]; - } - - // Set public as received remotely - if (!$local->public) - { - $db->updateUserPublic( - $local->userId, - true, - $remote->timeUpdated - ); - - $response['user'][] = [ - 'status' => true, - 'message' => sprintf( - _('Field "public" changed to newer value for user ID "%s"'), - $local->userId - ) - ]; - } - } - - break; - case 'magnet': - - if (!API_IMPORT_MAGNETS_ENABLED) - { - $response['magnet'][] = [ - 'status' => false, - 'message' => _('Magnets import disabled on this node. Related content skipped.') - ]; - - continue 2; - } - - // Validate remote fields - $error = []; - - if (!Valid::magnet($remote, $error)) - { - $response['magnet'][] = [ - 'status' => false, - 'message' => sprintf( - _('Magnet data mismatch protocol with error: %s'), - print_r($error, true) - ), - ]; - - continue 2; - } - - // User local alias required - if (!isset($aliasUserId[$remote->userId])) - { - $response['magnet'][] = [ - 'status' => false, - 'message' => _('User data relation not found for magnet'), - ]; - - continue 2; - } - - // Skip import on magnet approved required - if (API_IMPORT_MAGNETS_APPROVED_ONLY && !$remote->approved) - { - $response['magnet'][] = [ - 'status' => false, - 'message' => _('Node accepting approved magnets only') - ]; - - continue 2; - } - - /// Add new magnet if not exist by timestamp added for this user - if ($local = $db->findMagnet($aliasUserId[$remote->userId], $remote->timeAdded)) - { - $response['magnet'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet successfully associated with ID "%s"'), - $local->magnetId - ) - ]; - } - - /// Add and init new magnet if not exist - else if ($local = $db->getMagnet( - $db->addMagnet( - $aliasUserId[$remote->userId], - $remote->xl, - $remote->dn, - '', // @TODO linkSource used for debug only, will be deleted later - true, - $remote->comments, - $remote->sensitive, - MAGNET_AUTO_APPROVE_ON_IMPORT_APPROVED ? $remote->approved : MAGNET_DEFAULT_APPROVED, - $remote->timeAdded - ) - ) - ) - { - $response['magnet'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet successfully synced with ID "%s"'), - $local->magnetId - ) - ]; - } - - else - { - $response['magnet'][] = [ - 'status' => false, - 'message' => sprintf( - _('Could not init magnet: %s'), - $remote - ) - ]; - - continue 2; - } - - /// Add magnet alias for this host - $aliasMagnetId[$remote->magnetId] = $local->magnetId; - - /// Update time added if newer - if ($local->timeAdded < $remote->timeAdded) - { - $db->updateMagnetTimeAdded( - $local->magnetId, - $remote->timeAdded - ); - - $response['magnet'][] = [ - 'status' => true, - 'message' => sprintf( - _('Field "timeAdded" changed to newer value for magnet ID "%s"'), - $local->magnetId - ) - ]; - } - - /// Update info if remote newer - if ($local->timeUpdated < $remote->timeUpdated) - { - // Magnet fields - $db->updateMagnetXl($local->magnetId, $remote->xl, $remote->timeUpdated); - $db->updateMagnetDn($local->magnetId, $remote->dn, $remote->timeUpdated); - $db->updateMagnetTitle($local->magnetId, $remote->title, $remote->timeUpdated); - $db->updateMagnetPreview($local->magnetId, $remote->preview, $remote->timeUpdated); - $db->updateMagnetDescription($local->magnetId, $remote->description, $remote->timeUpdated); - $db->updateMagnetComments($local->magnetId, $remote->comments, $remote->timeUpdated); - $db->updateMagnetSensitive($local->magnetId, $remote->sensitive, $remote->timeUpdated); - - if (MAGNET_AUTO_APPROVE_ON_IMPORT_APPROVED && $local->approved !== $remote->approved && $remote->approved) - { - $db->updateMagnetApproved($local->magnetId, $remote->approved, $remote->timeUpdated); - } - - // xt - foreach ((array) $remote->xt as $xt) - { - switch ($xt->version) - { - case 1: - - $exist = false; - - foreach ($db->findMagnetToInfoHashByMagnetId($local->magnetId) as $result) - { - if ($infoHash = $db->getInfoHash($result->infoHashId)) - { - if ($infoHash->version == 1) - { - $exist = true; - } - } - } - - if (!$exist) - { - $db->addMagnetToInfoHash( - $local->magnetId, - $db->initInfoHashId( - $xt->value, 1 - ) - ); - } - - break; - - case 2: - - $exist = false; - - foreach ($db->findMagnetToInfoHashByMagnetId($local->magnetId) as $result) - { - if ($infoHash = $db->getInfoHash($result->infoHashId)) - { - if ($infoHash->version == 2) - { - $exist = true; - } - } - } - - if (!$exist) - { - $db->addMagnetToInfoHash( - $local->magnetId, - $db->initInfoHashId( - $xt->value, 2 - ) - ); - } - - break; - } - } - - // kt - $db->deleteMagnetToKeywordTopicByMagnetId($local->magnetId); - - foreach ($remote->kt as $kt) - { - $db->initMagnetToKeywordTopicId( - $local->magnetId, - $db->initKeywordTopicId(trim(mb_strtolower($kt))) - ); - } - - // tr - $db->deleteMagnetToAddressTrackerByMagnetId($local->magnetId); - - foreach ($remote->tr as $tr) - { - if ($url = Yggverse\Parser\Url::parse($tr)) - { - $db->initMagnetToAddressTrackerId( - $local->magnetId, - $db->initAddressTrackerId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - - // as - $db->deleteMagnetToAcceptableSourceByMagnetId($local->magnetId); - - foreach ($remote->as as $as) - { - if ($url = Yggverse\Parser\Url::parse($as)) - { - $db->initMagnetToAcceptableSourceId( - $local->magnetId, - $db->initAcceptableSourceId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - - // xs - $db->deleteMagnetToExactSourceByMagnetId($local->magnetId); - - foreach ($remote->xs as $xs) - { - if ($url = Yggverse\Parser\Url::parse($xs)) - { - $db->initMagnetToExactSourceId( - $local->magnetId, - $db->initExactSourceId( - $db->initSchemeId($url->host->scheme), - $db->initHostId($url->host->name), - $db->initPortId($url->host->port), - $db->initUriId($url->page->uri) - ) - ); - } - } - - $response['magnet'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet fields updated to newer version for magnet ID "%s"'), - $local->magnetId - ) - ]; - } - - break; - case 'magnetComment': - - if (!API_IMPORT_MAGNET_COMMENTS_ENABLED) - { - $response['magnetComment'][] = [ - 'status' => false, - 'message' => _('Magnet comments import disabled on this node') - ]; - - continue 2; - } - - // Validate - $error = []; - - if (!Valid::magnetComment($remote, $error)) - { - $response['magnetComment'][] = [ - 'status' => false, - 'message' => sprintf( - _('Magnet comment data mismatch protocol with error: %s'), - print_r($error, true) - ), - ]; - - continue 2; - } - - // Skip import on magnet approved required - if (API_IMPORT_MAGNET_COMMENTS_APPROVED_ONLY && !$remote->approved) - { - $response['magnetComment'][] = [ - 'status' => false, - 'message' => _('Node accepting approved magnet comments only: %s') - ]; - - continue 2; - } - - // User local alias required - if (!isset($aliasUserId[$remote->userId]) || !isset($aliasMagnetId[$remote->magnetId])) - { - $response['magnetComment'][] = [ - 'status' => false, - 'message' => _('Magnet comment data relation not found for: %s') - ]; - - continue 2; - } - - // Parent comment provided - if (is_int($remote->magnetCommentIdParent)) - { - $localMagnetCommentIdParent = null; // @TODO feature not in use yet - } - - else - { - $localMagnetCommentIdParent = null; - } - - // Magnet comment exists by timestamp added for this user - if ($local = $db->findMagnetComment($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->timeAdded)) - { - $response['magnetComment'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet comment successfully associated with ID "%s"'), - $local->magnetCommentId - ) - ]; - } - - // Magnet comment exists by timestamp added for this user, register new one - else if ($magnetCommentId = $db->addMagnetComment($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $localMagnetCommentIdParent, - $remote->value, - $remote->approved, - true, - $remote->timeAdded)) - { - $response['magnetComment'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet comment successfully synced with ID "%s"'), - $magnetCommentId - ) - ]; - } - - break; - case 'magnetDownload': - - // Magnet downloads - if (!API_IMPORT_MAGNET_DOWNLOADS_ENABLED) - { - $response['magnetDownload'][] = [ - 'status' => false, - 'message' => _('Magnet downloads import disabled on this node') - ]; - - continue 2; - } - - // Validate - $error = []; - - if (!Valid::magnetDownload($remote, $error)) - { - $response['magnetDownload'][] = [ - 'status' => false, - 'message' => sprintf( - _('Magnet download data mismatch protocol with error: %s'), - print_r($error, true) - ), - ]; - - continue 2; - } - - // User local alias required - if (!isset($aliasUserId[$remote->userId]) || !isset($aliasMagnetId[$remote->magnetId])) - { - $response['magnetDownload'][] = [ - 'status' => false, - 'message' => _('Magnet download data relation not found') - ]; - - continue 2; - } - - // Magnet download exists by timestamp added for this user - if ($local = $db->findMagnetDownload($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->timeAdded)) - { - $response['magnetDownload'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet download successfully associated with ID "%s"'), - $local->magnetDownloadId - ) - ]; - } - - // Magnet download exists by timestamp added for this user, register new one - else if ($magnetDownloadId = $db->addMagnetDownload($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->timeAdded)) - { - $response['magnetDownload'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet download successfully synced with ID "%s"'), - $magnetDownloadId - ) - ]; - } - - break; - case 'magnetStar': - - if (!API_IMPORT_MAGNET_STARS_ENABLED) - { - $response['magnetStar'][] = [ - 'status' => false, - 'message' => _('Magnet stars import disabled on this node') - ]; - - continue 2; - } - - // Validate - $error = []; - - if (!Valid::magnetStar($remote, $error)) - { - $response['magnetStar'][] = [ - 'status' => false, - 'message' => sprintf( - _('Magnet star data mismatch protocol with error: %s'), - print_r($error, true) - ), - ]; - - continue 2; - } - - // User local alias required - if (!isset($aliasUserId[$remote->userId]) || !isset($aliasMagnetId[$remote->magnetId])) - { - $response['magnetStar'][] = [ - 'status' => false, - 'message' => _('Magnet star data relation not found') - ]; - - continue 2; - } - - // Magnet star exists by timestamp added for this user - if ($local = $db->findMagnetStar($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->timeAdded)) - { - $response['magnetStar'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet star successfully associated with ID "%s"'), - $local->magnetStarId - ) - ]; - } - - // Magnet star exists by timestamp added for this user, register new one - else if ($magnetStarId = $db->addMagnetStar($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->value, - $remote->timeAdded)) - { - $response['magnetStar'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet star successfully synced with ID "%s"'), - $magnetStarId - ) - ]; - } - - break; - case 'magnetView': - - if (!API_IMPORT_MAGNET_VIEWS_ENABLED) - { - $response['magnetView'][] = [ - 'status' => false, - 'message' => _('Magnet views import disabled on this node') - ]; - - continue 2; - } - - // Validate - $error = []; - - if (!Valid::magnetView($remote, $error)) - { - $response['magnetView'][] = [ - 'status' => false, - 'message' => sprintf( - _('Magnet view data mismatch protocol with error: %s'), - print_r($error, true) - ), - ]; - - continue 2; - } - - // User local alias required - if (!isset($aliasUserId[$remote->userId]) || !isset($aliasMagnetId[$remote->magnetId])) - { - $response['magnetView'][] = [ - 'status' => false, - 'message' => _('Magnet view data relation not found for: %s') - ]; - - continue 2; - } - - // Magnet view exists by timestamp added for this user - if ($local = $db->findMagnetView($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->timeAdded)) - { - $response['magnetView'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet view successfully associated with ID "%s"'), - $local->magnetViewId - ) - ]; - } - - // Magnet view exists by timestamp added for this user, register new one - else if ($magnetViewId = $db->addMagnetView($aliasMagnetId[$remote->magnetId], - $aliasUserId[$remote->userId], - $remote->timeAdded)) - { - $response['magnetView'][] = [ - 'status' => true, - 'message' => sprintf( - _('Magnet view successfully synced with ID "%s"'), - $magnetViewId - ) - ]; - } - - break; - default: - - $response[$field][] = - [ - 'status' => false, - 'message' => _('Field "%s" not supported by protocol') - ]; - - continue 2; - } - } - - $db->commit(); - } - - catch (Exception $error) - { - $debug['exception'][] = print_r($error, true); - - $db->rollBack(); - } -} - -// Debug log -if (LOG_API_PUSH_ENABLED) -{ - @mkdir(LOG_DIRECTORY, 0770, true); - - if ($handle = fopen(LOG_DIRECTORY . '/' . LOG_API_PUSH_FILENAME, 'a+')) - { - $debug['time']['total'] = microtime(true) - $debug['time']['total']; - - $debug['memory']['total'] = memory_get_usage() - $debug['memory']['start']; - $debug['memory']['peaks'] = memory_get_peak_usage(); - - $debug['db']['total']['select'] = $db->getDebug()->query->select->total; - $debug['db']['total']['insert'] = $db->getDebug()->query->insert->total; - $debug['db']['total']['update'] = $db->getDebug()->query->update->total; - $debug['db']['total']['delete'] = $db->getDebug()->query->delete->total; - - fwrite( - $handle, - print_r( - [ - 'response' => $response, - 'debug' => $debug - ], - true - ) - ); - - fclose($handle); - - chmod(LOG_DIRECTORY . '/' . LOG_API_PUSH_FILENAME, 0770); - } -} - -// Output -header('Content-Type: application/json; charset=utf-8'); - -echo json_encode($response); \ No newline at end of file diff --git a/src/public/index.php b/src/public/index.php deleted file mode 100644 index 6a9b6e3..0000000 --- a/src/public/index.php +++ /dev/null @@ -1,4 +0,0 @@ - + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + diff --git a/templates/default/home/index.html.twig b/templates/default/home/index.html.twig new file mode 100644 index 0000000..fe403d6 --- /dev/null +++ b/templates/default/home/index.html.twig @@ -0,0 +1,2 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ 'Home'|trans }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig new file mode 100644 index 0000000..58b2433 --- /dev/null +++ b/templates/default/layout.html.twig @@ -0,0 +1,60 @@ + + + + + {% block title %}{{ name }}{% endblock %} + {% block stylesheets %} + + + {% endblock %} + + + {% block header %} +
+
+
+ + {% block header_search %} + {{ render(controller( + 'App\\Controller\\SearchController::module' + )) }} + {% endblock %} +
+
+
+ {% endblock %} + {% block main %} +
+
+
+
+ {% block main_profile %} + {{ render(controller( + 'App\\Controller\\ProfileController::module', + {route : app.request.get('_route')} + )) }} + {% endblock %} +
+ {% block main_content %}{% endblock %} +
+
+
+
+
+ {% endblock %} + {% block footer %} +
+
+
+
+ {% block footer_trackers %}{% endblock %} + GitHub +
+
+
+
+ {% endblock %} + + diff --git a/templates/default/page/submit.html.twig b/templates/default/page/submit.html.twig new file mode 100644 index 0000000..736ec81 --- /dev/null +++ b/templates/default/page/submit.html.twig @@ -0,0 +1,129 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{'Submit'|trans }} - {{ name }}{% endblock %} + +{% block main_content %} +
+

{{'Submit'|trans }}

+
+
+ +
+ + + + + + + +
+
+ + + + + + + {% for errors in form.title.error %} + {% for error in errors %} +
+ {{ error }} +
+ {% endfor %} + {% endfor %} + +
+
+ + + + + + + {% for errors in form.description.error %} + {% for error in errors %} +
+ {{ error }} +
+ {% endfor %} + {% endfor %} + +
+
+ + + + + + + {% for errors in form.keywords.error %} + {% for error in errors %} +
+ {{ error }} +
+ {% endfor %} + {% endfor %} + +
+
+ + + + + + + +
+
+ +
+
+{% endblock %} diff --git a/templates/default/profile/index.html.twig b/templates/default/profile/index.html.twig new file mode 100644 index 0000000..34c7353 --- /dev/null +++ b/templates/default/profile/index.html.twig @@ -0,0 +1,2 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ 'Profile - Settings'|trans }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/src/app/view/theme/default/module/profile.phtml b/templates/default/profile/module.html.twig similarity index 52% rename from src/app/view/theme/default/module/profile.phtml rename to templates/default/profile/module.html.twig index 5d54747..2277622 100644 --- a/src/app/view/theme/default/module/profile.phtml +++ b/templates/default/profile/module.html.twig @@ -1,199 +1,178 @@ -
-
- - identicon - +
+ -
- - +
+ {% if route == 'home_index' %} + - - + + {{ 'Home'|trans }} - - + {% else %} + - - + + {{ 'Home'|trans }} - - - - + {% endif %} + {% if route == 'page_stars' %} + - - + + {{ 'Stars'|trans }} - + {{ stars }} - - + {% else %} + - - + + {{ 'Stars'|trans }} - + {{ stars }} - - - + {% endif %} + {% if route == 'page_views' %} + - - + + {{ 'Views'|trans }} - + {{ views }} - - + {% else %} + - - + + {{ 'Views'|trans }} - + {{ views }} - - - + {% endif %} + {% if route == 'page_comments' %} + - - + + {{ 'Comments'|trans }} - + {{ comments }} - - + {% else %} + - - + + {{ 'Comments'|trans }} - + {{ comments }} - - - + {% endif %} + {% if route == 'page_downloads' %} + - - + + {{ 'Downloads'|trans }} - + {{ downloads }} - - + {% else %} + - - + + {{ 'Downloads'|trans }} - + {{ downloads }} - - - + {% endif %} + {% if route == 'page_editions' %} + - - + + {{ 'Editions'|trans }} - + {{ editions }} - - + {% else %} + - - + + {{ 'Editions'|trans }} - + {{ editions }} - - - + {% endif %} + {% if route == '/page/submit' %} + - - + + {{ 'Submit'|trans }} - - + {% else %} + - - + + {{ 'Submit'|trans }} - + {% endif %}
\ No newline at end of file diff --git a/templates/default/profile/setting.html.twig b/templates/default/profile/setting.html.twig new file mode 100644 index 0000000..34c7353 --- /dev/null +++ b/templates/default/profile/setting.html.twig @@ -0,0 +1,2 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ 'Profile - Settings'|trans }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/search/index.html.twig b/templates/default/search/index.html.twig new file mode 100644 index 0000000..9ede45e --- /dev/null +++ b/templates/default/search/index.html.twig @@ -0,0 +1,10 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ query }} - {{'Search'|trans }} - {{ name }}{% endblock %} +{% block header_search %} + {{ render(controller( + 'App\\Controller\\SearchController::module', + { + query : query + } + )) }} +{% endblock %} \ No newline at end of file diff --git a/templates/default/search/module.html.twig b/templates/default/search/module.html.twig new file mode 100644 index 0000000..f872234 --- /dev/null +++ b/templates/default/search/module.html.twig @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..469dcce --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,11 @@ +bootEnv(dirname(__DIR__).'/.env'); +} diff --git a/translations/.gitignore b/translations/.gitignore new file mode 100644 index 0000000..e69de29 From 06e06947397950787d32839676d9acf1377ed8ff Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 2 Oct 2023 16:15:51 +0300 Subject: [PATCH 082/434] update readme --- README.md | 138 ++---------------------------------------------------- 1 file changed, 3 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 6cb88b8..331abe9 100644 --- a/README.md +++ b/README.md @@ -4,57 +4,19 @@ Distributed BitTorrent Registry for Yggdrasil YGGtracker uses [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) IPv6 addresses to identify users without registration. -#### Nodes online - -YGGtracker is distributed index engine, default nodes list defined in [nodes.json](https://github.com/YGGverse/YGGtracker/blob/main/src/config/nodes.json) - -If you have launched new one, feel free to participate by PR. - -#### Trackers - -Open trackers defined in [trackers.json](https://github.com/YGGverse/YGGtracker/blob/main/src/config/trackers.json) - -* Application appends initial trackers to all download links and magnet forms -* Trackers not in list will be cropped by the application filter -* Feel free to PR new yggdrasil tracker! - -#### Public peers - -Traffic-oriented public peers for Yggdrasil defined in [peers.json](https://github.com/YGGverse/YGGtracker/blob/main/src/config/peers.json) - -#### Requirements - -``` -php8^ -php-pdo -php-mysql -php-curl -php-memcached -sphinxsearch -memcached -``` #### Installation -##### Production +##### Production (v.1) * `composer create-project yggverse/yggtracker` -##### Development +##### Development (v.2) * `git clone https://github.com/YGGverse/YGGtracker.git` * `cd YGGtracker` * `composer update` -#### Setup -* Server configuration `/example/environment` -* The web root dir is `/src/public` -* Deploy the database using [MySQL Workbench](https://www.mysql.com/products/workbench) project presented in the `/database` folder -* Install [Sphinx Search Server](https://sphinxsearch.com) -* Server environment examples presented at `/example/environment` folder -* App config available at `/src/config` folder in JSON format. - + To make environment-based configuration for JSON files, create subfolder `/src/config/env` and define `env` in `/src/config/.env` file - -#### Contribute +#### Contribution Please make new branch for each PR @@ -63,100 +25,6 @@ git checkout main git checkout -b my-pr-branch-name ``` -#### Roadmap - -* [ ] BitTorrent protocol - + [ ] Protocol - + [ ] announce - + [ ] announce-list - + [ ] comment - + [ ] created by - + [ ] creation date - + [ ] info - + [ ] file-duration - + [ ] file-media - + [ ] files - + [ ] name - + [ ] piece length - + [ ] pieces - + [ ] private - + [ ] profiles - -* [ ] Magnet protocol - + [x] Exact Topic / xt - + [x] Display Name / dn - + [x] eXact Length / xl - + [x] Address Tracker / rt - + [x] Web Seed / ws - + [x] Acceptable Source / as - + [x] eXact Source / xs - + [x] Keyword Topic / kt - + [ ] Manifest Topic / mt - + [ ] Select Only / so - + [ ] PEer / x.pe - -* [ ] Catalog - + [x] Public levels - + [x] Sensitive filter - + [x] Comments - + [x] Scrape trackers - + [x] Peers - + [x] Completed - + [x] Leechers - + [x] Stars - + [x] Views - + [x] Downloads - + [x] Wanted - + [x] Threading comments - + [ ] Forks - -* [ ] Profile - + [ ] Listing - + [ ] Uploads - + [ ] Downloads - + [ ] Stars - + [ ] Following - + [ ] Followers - + [ ] Comments - + [ ] Settings - + [ ] Public name - + [ ] Downloads customization - + [ ] Address Tracker - + [ ] Web Seed - + [ ] Acceptable Source - + [ ] eXact Source - + [ ] Content filters - -* [x] API - + [x] Active (push) - + [x] Magnet - + [x] Edit - + [x] Download - + [x] Comment - + [x] Star - + [x] View - + [x] Passive (feed) - + [x] Manifest - + [x] Users - + [x] Magnets - + [x] Downloads - + [x] Comments - + [x] Stars - + [x] Views - -* [x] Export - + [x] Sitemap - + [x] RSS - + [x] Magnets - + [x] Comments - -* [x] Other - + [x] Moderation - + [x] UI - + [ ] CLI - + [ ] Installation tools - - #### Donate to contributors * @d47081: From 89ac72b77d0390e03663117fe1a72c7e9a748236 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 00:25:48 +0300 Subject: [PATCH 083/434] implement time service --- src/Service/TimeService.php | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Service/TimeService.php diff --git a/src/Service/TimeService.php b/src/Service/TimeService.php new file mode 100644 index 0000000..cec6b8d --- /dev/null +++ b/src/Service/TimeService.php @@ -0,0 +1,61 @@ +translator = $translator; + } + + public function ago(int $time): string + { + $diff = time() - $time; + + if ($diff < 1) + { + return self::$translator->trans('now'); + } + + $values = + [ + 365 * 24 * 60 * 60 => $this->translator->trans('year'), + 30 * 24 * 60 * 60 => $this->translator->trans('month'), + 24 * 60 * 60 => $this->translator->trans('day'), + 60 * 60 => $this->translator->trans('hour'), + 60 => $this->translator->trans('minute'), + 1 => $this->translator->trans('second') + ]; + + $plural = [ + $this->translator->trans('year') => $this->translator->trans('years'), + $this->translator->trans('month') => $this->translator->trans('months'), + $this->translator->trans('day') => $this->translator->trans('days'), + $this->translator->trans('hour') => $this->translator->trans('hours'), + $this->translator->trans('minute') => $this->translator->trans('minutes'), + $this->translator->trans('second') => $this->translator->trans('seconds') + ]; + + foreach ($values as $key => $value) + { + $result = $diff / $key; + + if ($result >= 1) + { + $round = round($result); + + return sprintf( + '%s %s %s', + $round, + $round > 1 ? $plural[$value] : $value, + $this->translator->trans('ago') + ); + } + } + } +} \ No newline at end of file From 4720b34e9cfc730c72ff5b3061df8656ac411b87 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 00:30:35 +0300 Subject: [PATCH 084/434] remove center alignment --- templates/default/layout.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 58b2433..1227001 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -36,7 +36,7 @@ {route : app.request.get('_route')} )) }} {% endblock %} -
+
{% block main_content %}{% endblock %}
From 8d1c35360b5ba15297990c02a3f005c1577e80ba Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 17:02:00 +0300 Subject: [PATCH 085/434] init locale settings #19 --- .env | 5 ++++- config/services.yaml | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env b/.env index ff72acd..082bfb8 100644 --- a/.env +++ b/.env @@ -47,4 +47,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 # YGGtracker APP_VERSION='2.0.0' -APP_NAME=YGGtracker \ No newline at end of file +APP_NAME=YGGtracker + +APP_LOCALE=en +APP_LOCALES=en|uk diff --git a/config/services.yaml b/config/services.yaml index afb739c..df414d4 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -6,6 +6,8 @@ parameters: app.version: '%env(APP_VERSION)%' app.name: '%env(APP_NAME)%' + app.locale: '%env(APP_LOCALE)%' + app.locales: '%env(APP_LOCALES)%' services: # default configuration for services in *this* file From 737b79b608d64caa7a483c4b76a3a63837c552a3 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 17:24:34 +0300 Subject: [PATCH 086/434] fix static call --- src/Service/TimeService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/TimeService.php b/src/Service/TimeService.php index cec6b8d..d6cd530 100644 --- a/src/Service/TimeService.php +++ b/src/Service/TimeService.php @@ -19,7 +19,7 @@ class TimeService if ($diff < 1) { - return self::$translator->trans('now'); + return $this->translator->trans('now'); } $values = From ae8ec4823a9a744fb90dd38c0f44ef562b48c165 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 22:35:13 +0300 Subject: [PATCH 087/434] implement profile page #17 --- .env | 2 +- public/asset/default/css/common.css | 2 +- public/asset/default/css/framework.css | 22 ++- src/Controller/DashboardController.php | 33 ++++ src/Controller/HomeController.php | 20 -- src/Controller/PageController.php | 5 + src/Controller/ProfileController.php | 76 ------- src/Controller/UserController.php | 161 +++++++++++++++ src/Entity/User.php | 76 +++---- src/Repository/UserRepository.php | 41 ++-- src/Service/User.php | 12 -- src/Service/UserService.php | 63 ++++++ .../{home => dashboard}/index.html.twig | 0 templates/default/layout.html.twig | 6 +- templates/default/profile/index.html.twig | 2 - templates/default/profile/module.html.twig | 178 ----------------- templates/default/profile/setting.html.twig | 2 - templates/default/user/info.html.twig | 92 +++++++++ templates/default/user/module.html.twig | 186 ++++++++++++++++++ templates/default/user/profile.html.twig | 105 ++++++++++ templates/redirect/index.html.twig | 20 ++ 21 files changed, 740 insertions(+), 364 deletions(-) create mode 100644 src/Controller/DashboardController.php delete mode 100644 src/Controller/HomeController.php delete mode 100644 src/Controller/ProfileController.php create mode 100644 src/Controller/UserController.php delete mode 100644 src/Service/User.php create mode 100644 src/Service/UserService.php rename templates/default/{home => dashboard}/index.html.twig (100%) delete mode 100644 templates/default/profile/index.html.twig delete mode 100644 templates/default/profile/module.html.twig delete mode 100644 templates/default/profile/setting.html.twig create mode 100644 templates/default/user/info.html.twig create mode 100644 templates/default/user/module.html.twig create mode 100644 templates/default/user/profile.html.twig create mode 100644 templates/redirect/index.html.twig diff --git a/.env b/.env index 082bfb8..618d0fe 100644 --- a/.env +++ b/.env @@ -50,4 +50,4 @@ APP_VERSION='2.0.0' APP_NAME=YGGtracker APP_LOCALE=en -APP_LOCALES=en|uk +APP_LOCALES=en|uk \ No newline at end of file diff --git a/public/asset/default/css/common.css b/public/asset/default/css/common.css index 8f07131..cb5d2ff 100644 --- a/public/asset/default/css/common.css +++ b/public/asset/default/css/common.css @@ -54,7 +54,7 @@ textarea { color: #ccc; border: 0; border-radius: 3px; - padding: 6px 8px; + padding: 8px; font-size: 13px; } diff --git a/public/asset/default/css/framework.css b/public/asset/default/css/framework.css index 65e4289..21368cb 100644 --- a/public/asset/default/css/framework.css +++ b/public/asset/default/css/framework.css @@ -118,6 +118,10 @@ a.label-green:hover { position: fixed; } +.position-absolute { + position: absolute; +} + .vertical-align-middle { vertical-align: middle; } @@ -170,7 +174,7 @@ a.label-green:hover { border-top: 1px #5d627d solid; } -.border-width-2 { +.border-width-2-px { border-width: 2px; } @@ -299,6 +303,10 @@ a:visited.background-color-hover-night-light:hover { padding: 16px; } +.padding-24-px { + padding: 24px; +} + .margin-l-4-px { margin-left: 4px; } @@ -328,6 +336,10 @@ a:visited.background-color-hover-night-light:hover { margin-left: 12px; } +.margin-l-96-px { + margin-left: 96px; +} + .margin-l--196-px { margin-left: -196px; } @@ -366,6 +378,10 @@ a:visited.background-color-hover-night-light:hover { display: block; } +.display-flex { + display: flex; +} + .opacity-0 { opacity: 0; } @@ -410,6 +426,10 @@ a:visited.background-color-hover-night-light:hover { width: 80%; } +.width-80-px { + width: 80px; +} + .width-180-px { width: 180px; } diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php new file mode 100644 index 0000000..13ba30a --- /dev/null +++ b/src/Controller/DashboardController.php @@ -0,0 +1,33 @@ +redirectToRoute( + 'dashboard_index', + [ + '_locale' => 'en' + ] + ); + } + + #[Route( + '/{_locale}', + name: 'dashboard_index' + )] + public function index(Request $request): Response + { + return $this->render( + 'default/dashboard/index.html.twig' + ); + } +} \ No newline at end of file diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php deleted file mode 100644 index ee38aa2..0000000 --- a/src/Controller/HomeController.php +++ /dev/null @@ -1,20 +0,0 @@ -render('default/home/index.html.twig'); - } -} \ No newline at end of file diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index 7c45a43..8499718 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -15,6 +15,11 @@ class PageController extends AbstractController )] public function submit(): Response { + /* + return $this->redirectToRoute('page', [ + 'id' => $page->getId() + ]); + */ return $this->render('default/page/submit.html.twig', [ // @TODO ]); diff --git a/src/Controller/ProfileController.php b/src/Controller/ProfileController.php deleted file mode 100644 index c8a3b80..0000000 --- a/src/Controller/ProfileController.php +++ /dev/null @@ -1,76 +0,0 @@ -render( - 'default/profile/index.html.twig', - [ - 'user' => $user->init($request->getClientIp()) - ] - ); - } - - #[Route( - '/{_locale}/profile/setting', - name: 'profile_setting' - )] - public function setting(): Response - { - // @TODO - return $this->render( - 'default/profile/setting.html.twig' - ); - } - - public function module(string $route = ''): Response - { - return $this->render( - 'default/profile/module.html.twig', - [ - 'route' => $route, - 'stars' => 0, - 'views' => 0, - 'comments' => 0, - 'downloads' => 0, - 'editions' => 0, - 'identicon' => $this->_getIdenticon( - '@TODO', - 17, - [ - 'backgroundColor' => 'rgba(255, 255, 255, 0)', - ] - ) - ] - ); - } - - private function _getIdenticon( - mixed $id, - int $size, - array $style, - string $format = 'webp') : string - { - $identicon = new \Jdenticon\Identicon(); - - $identicon->setValue($id); - $identicon->setSize($size); - $identicon->setStyle($style); - - return $identicon->getImageDataUri($format); - } -} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..c0b81ec --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,161 @@ + '%app.locale%' + ], + requirements: [ + '_locale' => '%app.locales%', + ], + )] + public function profile( + Request $request, + UserService $userService, + TimeService $timeService): Response + { + // Init user + $user = $userService->init( + $request->getClientIp() + ); + + // Process post request + if ($request->isMethod('post')) + { + // Update locale + if (in_array($request->get('locale'), explode('|', $this->getParameter('app.locales')))) + { + $user->setLocale( + $request->get('locale') + ); + } + + // Update locales + if ($request->get('locales')) + { + $user->setLocales( + $request->get('locales') + ); + } + + // Save changes to DB + $userService->save($user); + } + + // Generate identicon + $identicon = new \Jdenticon\Identicon(); + + $identicon->setValue($user->getAddress()); + $identicon->setSize(48); + $identicon->setStyle( + [ + 'backgroundColor' => 'rgba(255, 255, 255, 0)', + 'padding' => 0 + ] + ); + + // Render template + return $this->render( + 'default/user/profile.html.twig', + [ + 'user' => [ + 'id' => $user->getId(), + 'address' => $request->getClientIp() == $user->getAddress() ? $user->getAddress() : false, + 'moderator' => $user->isModerator(), + 'approved' => $user->isApproved(), + 'status' => $user->isStatus(), + 'locale' => $user->getLocale(), + 'locales' => $user->getLocales(), + 'added' => $timeService->ago( + $user->getAdded() + ), + 'identicon' => $identicon->getImageDataUri('webp'), + ], + 'locales' => explode('|', $this->getParameter('app.locales')) + ] + ); + } + + #[Route( + '/{_locale}/user/{id}', + name: 'user_info', + defaults: [ + '_locale' => '%app.locale%' + ], + requirements: [ + '_locale' => '%app.locales%', + ], + )] + public function info( + int $id, + Request $request, + UserService $userService, + TimeService $timeService): Response + { + // Init user + if (!$user = $userService->get($id)) + { + throw $this->createNotFoundException(); + } + + // Generate identicon + $identicon = new \Jdenticon\Identicon(); + + $identicon->setValue($user->getAddress()); + $identicon->setSize(48); + $identicon->setStyle( + [ + 'backgroundColor' => 'rgba(255, 255, 255, 0)', + 'padding' => 0 + ] + ); + + // Render template + return $this->render( + 'default/user/info.html.twig', + [ + 'user' => [ + 'id' => $user->getId(), + 'address' => $request->getClientIp() == $user->getAddress() ? $user->getAddress() : false, + 'moderator' => $user->isModerator(), + 'approved' => $user->isApproved(), + 'status' => $user->isStatus(), + 'locale' => $user->getLocale(), + 'locales' => $user->getLocales(), + 'added' => $timeService->ago( + $user->getAdded() + ), + 'identicon' => $identicon->getImageDataUri('webp'), + ] + ] + ); + } + + public function module(string $route = ''): Response + { + return $this->render( + 'default/user/module.html.twig', + [ + 'route' => $route, + 'stars' => 0, + 'views' => 0, + 'comments' => 0, + 'downloads' => 0, + 'editions' => 0, + ] + ); + } +} \ No newline at end of file diff --git a/src/Entity/User.php b/src/Entity/User.php index cf631a7..fba0937 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -3,6 +3,7 @@ namespace App\Entity; use App\Repository\UserRepository; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: UserRepository::class)] @@ -19,15 +20,6 @@ class User #[ORM\Column] private ?int $added = null; - #[ORM\Column] - private ?int $updated = null; - - #[ORM\Column] - private ?int $visited = null; - - #[ORM\Column] - private ?bool $public = null; - #[ORM\Column] private ?bool $moderator = null; @@ -37,6 +29,12 @@ class User #[ORM\Column] private ?bool $status = null; + #[ORM\Column(length: 2)] + private ?string $locale = null; + + #[ORM\Column(type: Types::ARRAY)] + private array $locales = []; + public function getId(): ?int { return $this->id; @@ -73,42 +71,6 @@ class User return $this; } - public function getUpdated(): ?int - { - return $this->updated; - } - - public function setUpdated(int $updated): static - { - $this->updated = $updated; - - return $this; - } - - public function getVisited(): ?int - { - return $this->visited; - } - - public function setVisited(int $visited): static - { - $this->visited = $visited; - - return $this; - } - - public function isPublic(): ?bool - { - return $this->public; - } - - public function setPublic(bool $public): static - { - $this->public = $public; - - return $this; - } - public function isModerator(): ?bool { return $this->moderator; @@ -144,4 +106,28 @@ class User return $this; } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(string $locale): static + { + $this->locale = $locale; + + return $this; + } + + public function getLocales(): array + { + return $this->locales; + } + + public function setLocales(array $locales): static + { + $this->locales = $locales; + + return $this; + } } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 3767526..49896f3 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -21,28 +21,23 @@ class UserRepository extends ServiceEntityRepository parent::__construct($registry, User::class); } -// /** -// * @return User[] Returns an array of User objects -// */ -// public function findByExampleField($value): array -// { -// return $this->createQueryBuilder('u') -// ->andWhere('u.exampleField = :val') -// ->setParameter('val', $value) -// ->orderBy('u.id', 'ASC') -// ->setMaxResults(10) -// ->getQuery() -// ->getResult() -// ; -// } + public function findOneByIdField(int $id): ?User + { + return $this->createQueryBuilder('u') + ->where('u.id = :id') + ->setParameter('id', $id) + ->getQuery() + ->getOneOrNullResult() + ; + } -// public function findOneBySomeField($value): ?User -// { -// return $this->createQueryBuilder('u') -// ->andWhere('u.exampleField = :val') -// ->setParameter('val', $value) -// ->getQuery() -// ->getOneOrNullResult() -// ; -// } + public function findOneByAddressField(string $address): ?User + { + return $this->createQueryBuilder('u') + ->where('u.address = :address') + ->setParameter('address', $address) + ->getQuery() + ->getOneOrNullResult() + ; + } } diff --git a/src/Service/User.php b/src/Service/User.php deleted file mode 100644 index 98890e0..0000000 --- a/src/Service/User.php +++ /dev/null @@ -1,12 +0,0 @@ -entityManager = $entityManager; + $this->userRepository = $entityManager->getRepository(User::class); + $this->parameterBagInterface = $parameterBagInterface; + } + + public function init(string $address): User + { + // Return existing user + if ($result = $this->userRepository->findOneByAddressField($address)) + { + return $result; + } + + // Create new user + $user = new User(); + + $user->setAddress($address); + $user->setAdded(time()); + $user->setApproved(false); + $user->setModerator(false); + $user->setStatus(true); + $user->setLocale( + $this->parameterBagInterface->get('app.locale') + ); + $user->setLocales( + explode('|', $this->parameterBagInterface->get('app.locales')) + ); + + $this->save($user); + + // Return user data + return $user; + } + + public function get(int $id): ?User + { + return $this->userRepository->findOneByIdField($id); + } + + public function save(User $user) : void + { + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} \ No newline at end of file diff --git a/templates/default/home/index.html.twig b/templates/default/dashboard/index.html.twig similarity index 100% rename from templates/default/home/index.html.twig rename to templates/default/dashboard/index.html.twig diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 1227001..11a0a05 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -13,7 +13,7 @@
- {% block header_search %} @@ -32,11 +32,11 @@
{% block main_profile %} {{ render(controller( - 'App\\Controller\\ProfileController::module', + 'App\\Controller\\UserController::module', {route : app.request.get('_route')} )) }} {% endblock %} -
+
{% block main_content %}{% endblock %}
diff --git a/templates/default/profile/index.html.twig b/templates/default/profile/index.html.twig deleted file mode 100644 index 34c7353..0000000 --- a/templates/default/profile/index.html.twig +++ /dev/null @@ -1,2 +0,0 @@ -{% extends 'default/layout.html.twig' %} -{% block title %}{{ 'Profile - Settings'|trans }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/profile/module.html.twig b/templates/default/profile/module.html.twig deleted file mode 100644 index 2277622..0000000 --- a/templates/default/profile/module.html.twig +++ /dev/null @@ -1,178 +0,0 @@ -
- -
- {% if route == 'home_index' %} - - - - - - {{ 'Home'|trans }} - - - {% else %} - - - - - - {{ 'Home'|trans }} - - - {% endif %} - {% if route == 'page_stars' %} - - - - - - {{ 'Stars'|trans }} - - - {{ stars }} - - - {% else %} - - - - - - {{ 'Stars'|trans }} - - - {{ stars }} - - - {% endif %} - {% if route == 'page_views' %} - - - - - - {{ 'Views'|trans }} - - - {{ views }} - - - {% else %} - - - - - - {{ 'Views'|trans }} - - - {{ views }} - - - {% endif %} - {% if route == 'page_comments' %} - - - - - - {{ 'Comments'|trans }} - - - {{ comments }} - - - {% else %} - - - - - - {{ 'Comments'|trans }} - - - {{ comments }} - - - {% endif %} - {% if route == 'page_downloads' %} - - - - - - {{ 'Downloads'|trans }} - - - {{ downloads }} - - - {% else %} - - - - - - {{ 'Downloads'|trans }} - - - {{ downloads }} - - - {% endif %} - {% if route == 'page_editions' %} - - - - - - {{ 'Editions'|trans }} - - - {{ editions }} - - - {% else %} - - - - - - {{ 'Editions'|trans }} - - - {{ editions }} - - - {% endif %} - {% if route == '/page/submit' %} - - - - - - {{ 'Submit'|trans }} - - - {% else %} - - - - - - {{ 'Submit'|trans }} - - - {% endif %} -
-
\ No newline at end of file diff --git a/templates/default/profile/setting.html.twig b/templates/default/profile/setting.html.twig deleted file mode 100644 index 34c7353..0000000 --- a/templates/default/profile/setting.html.twig +++ /dev/null @@ -1,2 +0,0 @@ -{% extends 'default/layout.html.twig' %} -{% block title %}{{ 'Profile - Settings'|trans }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/user/info.html.twig b/templates/default/user/info.html.twig new file mode 100644 index 0000000..e4caf5c --- /dev/null +++ b/templates/default/user/info.html.twig @@ -0,0 +1,92 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ 'User'|trans }} #{{ user.id }} - {{ name }}{% endblock %} +{% block main_content %} +
+ identicon +
+

{{ 'Profile'|trans }}

+ + + + + + {% if user.address %} + + + + + + + + + {% else %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'Common'|trans }}
{{ 'Address'|trans }} + {{ user.address }} + + + + + + + + +
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Access'|trans }}
{{ 'Status'|trans }} + {% if user.status %} + {{ 'active'|trans }} + {% else %} + {{ 'disabled'|trans }} + {% endif %} +
{{ 'Approved'|trans }} + {% if user.approved %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Moderator'|trans }} + {% if user.moderator %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Settings'|trans }}
{{ 'Interface'|trans }} + {{ user.locale|trans }} +
{{ 'Content filter'|trans }} + {% for locale in user.locales %} +
+ {{ locale|trans }} +
+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/default/user/module.html.twig b/templates/default/user/module.html.twig new file mode 100644 index 0000000..366eb66 --- /dev/null +++ b/templates/default/user/module.html.twig @@ -0,0 +1,186 @@ +
+
+ {% if route == 'dashboard_index' %} + + + + + + {{ 'Home'|trans }} + + + {% else %} + + + + + + {{ 'Home'|trans }} + + + {% endif %} + {% if route == 'user_profile' %} + + + + + + {{ 'Profile'|trans }} + + + {% else %} + + + + + + {{ 'Profile'|trans }} + + + {% endif %} + {% if route == 'page_stars' %} + + + + + + {{ 'Stars'|trans }} + + + {{ stars }} + + + {% else %} + + + + + + {{ 'Stars'|trans }} + + + {{ stars }} + + + {% endif %} + {% if route == 'page_views' %} + + + + + + {{ 'Views'|trans }} + + + {{ views }} + + + {% else %} + + + + + + {{ 'Views'|trans }} + + + {{ views }} + + + {% endif %} + {% if route == 'page_comments' %} + + + + + + {{ 'Comments'|trans }} + + + {{ comments }} + + + {% else %} + + + + + + {{ 'Comments'|trans }} + + + {{ comments }} + + + {% endif %} + {% if route == 'page_downloads' %} + + + + + + {{ 'Downloads'|trans }} + + + {{ downloads }} + + + {% else %} + + + + + + {{ 'Downloads'|trans }} + + + {{ downloads }} + + + {% endif %} + {% if route == 'page_editions' %} + + + + + + {{ 'Editions'|trans }} + + + {{ editions }} + + + {% else %} + + + + + + {{ 'Editions'|trans }} + + + {{ editions }} + + + {% endif %} + {% if route == 'page_submit' %} + + + + + + {{ 'Submit'|trans }} + + + {% else %} + + + + + + {{ 'Submit'|trans }} + + + {% endif %} +
+
\ No newline at end of file diff --git a/templates/default/user/profile.html.twig b/templates/default/user/profile.html.twig new file mode 100644 index 0000000..e287961 --- /dev/null +++ b/templates/default/user/profile.html.twig @@ -0,0 +1,105 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ 'Profile'|trans }} - {{ name }}{% endblock %} +{% block main_content %} +
+
+ identicon +
+

{{ 'Profile'|trans }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'Common'|trans }}
{{ 'Address'|trans }} + {{ user.address }} + + + + + + + + +
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Access'|trans }}
{{ 'Status'|trans }} + {% if user.status %} + {{ 'active'|trans }} + {% else %} + {{ 'disabled'|trans }} + {% endif %} +
{{ 'Approved'|trans }} + {% if user.approved %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Moderator'|trans }} + {% if user.moderator %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Settings'|trans }}
{{ 'Interface'|trans }} + +
{{ 'Content filter'|trans }} + {% for locale in locales %} +
+ {% if locale in user.locales %} + + {% else %} + + {% endif %} + +
+ {% endfor %} +
+
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/redirect/index.html.twig b/templates/redirect/index.html.twig new file mode 100644 index 0000000..18beeb9 --- /dev/null +++ b/templates/redirect/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello RedirectController!{% endblock %} + +{% block body %} + + +
+

Hello {{ controller_name }}! ✅

+ + This friendly message is coming from: + +
+{% endblock %} From 6b2e67f04bb80c17ab39a5409d3428e0082ede68 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 22:41:42 +0300 Subject: [PATCH 088/434] redirect to env default language on empty _lang request #19 --- src/Controller/DashboardController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 13ba30a..8884edb 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -15,7 +15,7 @@ class DashboardController extends AbstractController return $this->redirectToRoute( 'dashboard_index', [ - '_locale' => 'en' + '_locale' => $this->getParameter('app.locale') ] ); } From f85414fd2b55dfb477653bb4c8b2c8662ac10829 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 22:42:35 +0300 Subject: [PATCH 089/434] rename method --- src/Controller/DashboardController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 8884edb..ac8b665 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -10,7 +10,7 @@ use Symfony\Component\HttpFoundation\Request; class DashboardController extends AbstractController { #[Route('/')] - public function indexNoLocale(): Response + public function root(): Response { return $this->redirectToRoute( 'dashboard_index', From 9fcc5a451b601c2561ee3da3ede47b8832441bc2 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 22:52:26 +0300 Subject: [PATCH 090/434] make redirect to user locale selected #19 --- src/Controller/DashboardController.php | 14 ++++++++++++-- src/Controller/UserController.php | 11 ++++++++++- src/Service/UserService.php | 5 ++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index ac8b665..817fd3b 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -7,15 +7,25 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; +use App\Service\UserService; + class DashboardController extends AbstractController { #[Route('/')] - public function root(): Response + public function root( + Request $request, + UserService $userService + ): Response { + // Init user + $user = $userService->init( + $request->getClientIp() + ); + return $this->redirectToRoute( 'dashboard_index', [ - '_locale' => $this->getParameter('app.locale') + '_locale' => $user->getLocale() ] ); } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index c0b81ec..5d685f7 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -25,7 +25,8 @@ class UserController extends AbstractController public function profile( Request $request, UserService $userService, - TimeService $timeService): Response + TimeService $timeService + ): Response { // Init user $user = $userService->init( @@ -53,6 +54,14 @@ class UserController extends AbstractController // Save changes to DB $userService->save($user); + + // Redirect user to new locale + return $this->redirectToRoute( + 'user_profile', + [ + '_locale' => $user->getLocale() + ] + ); } // Generate identicon diff --git a/src/Service/UserService.php b/src/Service/UserService.php index fc396c4..856fa9e 100644 --- a/src/Service/UserService.php +++ b/src/Service/UserService.php @@ -14,7 +14,10 @@ class UserService private UserRepository $userRepository; private ParameterBagInterface $parameterBagInterface; - public function __construct(EntityManagerInterface $entityManager, ParameterBagInterface $parameterBagInterface) + public function __construct( + EntityManagerInterface $entityManager, + ParameterBagInterface $parameterBagInterface + ) { $this->entityManager = $entityManager; $this->userRepository = $entityManager->getRepository(User::class); From b863d145a094231a3022f6c340f4ebd6a2daace5 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 23:00:10 +0300 Subject: [PATCH 091/434] remove demo templates --- templates/base.html.twig | 16 ---------------- templates/redirect/index.html.twig | 20 -------------------- 2 files changed, 36 deletions(-) delete mode 100644 templates/base.html.twig delete mode 100644 templates/redirect/index.html.twig diff --git a/templates/base.html.twig b/templates/base.html.twig deleted file mode 100644 index 67598ac..0000000 --- a/templates/base.html.twig +++ /dev/null @@ -1,16 +0,0 @@ - - - - - {% block title %}Welcome!{% endblock %} - - {% block stylesheets %} - {% endblock %} - - {% block javascripts %} - {% endblock %} - - - {% block body %}{% endblock %} - - diff --git a/templates/redirect/index.html.twig b/templates/redirect/index.html.twig deleted file mode 100644 index 18beeb9..0000000 --- a/templates/redirect/index.html.twig +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}Hello RedirectController!{% endblock %} - -{% block body %} - - -
-

Hello {{ controller_name }}! ✅

- - This friendly message is coming from: - -
-{% endblock %} From 7750a214e081fcc61a6595afedd4285c974e3e93 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 23:10:29 +0300 Subject: [PATCH 092/434] add crowdin link #19 --- templates/default/layout.html.twig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 11a0a05..3018a31 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -50,6 +50,8 @@
{% block footer_trackers %}{% endblock %} + {{'Translate'|trans }} + | GitHub
From 0224fbf23a669aef5a3be98169271acb1a73af35 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 23:30:50 +0300 Subject: [PATCH 093/434] integrate twig/intl-extra to make locale codes translation #19 --- composer.json | 2 + composer.lock | 133 ++++++++++++++++++++++- templates/default/user/info.html.twig | 8 +- templates/default/user/profile.html.twig | 10 +- 4 files changed, 143 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 19f9be0..d53f074 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,8 @@ "symfony/web-link": "6.3.*", "symfony/yaml": "6.3.*", "twig/extra-bundle": "^2.12|^3.0", + "twig/intl-extra": "^3.7", + "twig/string-extra": "^3.7", "twig/twig": "^2.12|^3.0" }, "config": { diff --git a/composer.lock b/composer.lock index 2f8b33c..9dbd264 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "12e4a495651b5797393aa13acc4d132f", + "content-hash": "42f94769f35af5500e3ae6ce20dcb64e", "packages": [ { "name": "christeredvartsen/php-bittorrent", @@ -7344,6 +7344,137 @@ ], "time": "2023-07-29T15:34:56+00:00" }, + { + "name": "twig/intl-extra", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/intl-extra.git", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4f4fe572f635534649cc069e1dafe4a8ad63774d", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/intl": "^5.4|^6.0", + "twig/twig": "^2.7|^3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4|^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\Extra\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Intl", + "homepage": "https://twig.symfony.com", + "keywords": [ + "intl", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/intl-extra/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-07-29T15:34:56+00:00" + }, + { + "name": "twig/string-extra", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/string-extra.git", + "reference": "7230d630a25e91cd91a2bd8e2f0e872962507eab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/string-extra/zipball/7230d630a25e91cd91a2bd8e2f0e872962507eab", + "reference": "7230d630a25e91cd91a2bd8e2f0e872962507eab", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/string": "^5.4|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.7|^3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4|^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\Extra\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Symfony String", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "string", + "twig", + "unicode" + ], + "support": { + "source": "https://github.com/twigphp/string-extra/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-07-29T15:34:56+00:00" + }, { "name": "twig/twig", "version": "v3.7.1", diff --git a/templates/default/user/info.html.twig b/templates/default/user/info.html.twig index e4caf5c..b3fe873 100644 --- a/templates/default/user/info.html.twig +++ b/templates/default/user/info.html.twig @@ -74,17 +74,13 @@ {{ 'Interface'|trans }} - {{ user.locale|trans }} + {{ user.locale|locale_name(user.locale)|u.title }} {{ 'Content filter'|trans }} - {% for locale in user.locales %} -
- {{ locale|trans }} -
- {% endfor %} + {% for i, locale in user.locales %}{% if i > 0 %},{% endif %} {{ locale|locale_name(locale)|u.title }}{% endfor %} diff --git a/templates/default/user/profile.html.twig b/templates/default/user/profile.html.twig index e287961..01da47c 100644 --- a/templates/default/user/profile.html.twig +++ b/templates/default/user/profile.html.twig @@ -71,9 +71,13 @@ @@ -90,7 +94,7 @@ {% endif %}
{% endfor %} From b1e5d524ac7c71c62d622e710c5cedff8ac2e960 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 23:40:49 +0300 Subject: [PATCH 094/434] init alt translation dump #19 --- translations/messages+intl-icu.uk.xlf | 178 ++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 translations/messages+intl-icu.uk.xlf diff --git a/translations/messages+intl-icu.uk.xlf b/translations/messages+intl-icu.uk.xlf new file mode 100644 index 0000000..c0a45c2 --- /dev/null +++ b/translations/messages+intl-icu.uk.xlf @@ -0,0 +1,178 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Translate + Translate + + + Home + Home + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Profile + Profile + + + Stars + Stars + + + Views + Views + + + Comments + Comments + + + Downloads + Downloads + + + Editions + Editions + + + Submit + Submit + + + User + User + + + Common + Common + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Save + Save + + +
+
From 4852882243534759ed1cbbffafc6626b06995789 Mon Sep 17 00:00:00 2001 From: ghost Date: Tue, 3 Oct 2023 23:58:43 +0300 Subject: [PATCH 095/434] add more locales #19 --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 618d0fe..d49f3c3 100644 --- a/.env +++ b/.env @@ -50,4 +50,4 @@ APP_VERSION='2.0.0' APP_NAME=YGGtracker APP_LOCALE=en -APP_LOCALES=en|uk \ No newline at end of file +APP_LOCALES=de|fr|en|uk \ No newline at end of file From 90f28cfd8bea20b24ec449e1eeade98a968207e7 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 15:35:54 +0300 Subject: [PATCH 096/434] add missed locales whitelist validation #19 --- src/Controller/UserController.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 5d685f7..af88166 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -47,8 +47,17 @@ class UserController extends AbstractController // Update locales if ($request->get('locales')) { + $locales = []; + foreach ((array) $request->get('locales') as $locale) + { + if (in_array($locale, explode('|', $this->getParameter('app.locales')))) + { + $locales[] = $locale; + } + } + $user->setLocales( - $request->get('locales') + $locales ); } From 4b1b80b875618585d69e4b162f67d95e9843db36 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 15:52:43 +0300 Subject: [PATCH 097/434] add dev project initiation example --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 331abe9..d833dc2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ YGGtracker uses [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) I * `git clone https://github.com/YGGverse/YGGtracker.git` * `cd YGGtracker` * `composer update` +* `php bin/console doctrine:schema:update --force` +* `symfony server:start` #### Contribution From af72fa14c4a192b59e5720fd7e132aff707032e1 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 17:09:15 +0300 Subject: [PATCH 098/434] remove row paddings by default --- public/asset/default/css/framework.css | 1 - 1 file changed, 1 deletion(-) diff --git a/public/asset/default/css/framework.css b/public/asset/default/css/framework.css index 21368cb..aa38b76 100644 --- a/public/asset/default/css/framework.css +++ b/public/asset/default/css/framework.css @@ -8,7 +8,6 @@ .row { position: relative; overflow: hidden; - padding: 8px; } .column { From 0aa38c16dd5d264c5716304dd5a030878bf52427 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 17:19:54 +0300 Subject: [PATCH 099/434] add identicon alt text translation #19 --- templates/default/user/info.html.twig | 2 +- templates/default/user/profile.html.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/default/user/info.html.twig b/templates/default/user/info.html.twig index b3fe873..d837cd5 100644 --- a/templates/default/user/info.html.twig +++ b/templates/default/user/info.html.twig @@ -2,7 +2,7 @@ {% block title %}{{ 'User'|trans }} #{{ user.id }} - {{ name }}{% endblock %} {% block main_content %}
- identicon + {{ 'identicon'|trans }}

{{ 'Profile'|trans }}

diff --git a/templates/default/user/profile.html.twig b/templates/default/user/profile.html.twig index 01da47c..3f9909f 100644 --- a/templates/default/user/profile.html.twig +++ b/templates/default/user/profile.html.twig @@ -3,7 +3,7 @@ {% block main_content %}
- identicon + {{ 'identicon'|trans }}

{{ 'Profile'|trans }}

From 8eb5a05f293e2dc9a4169b90f60a1e0515581bb6 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 17:44:56 +0300 Subject: [PATCH 100/434] move container inside the main_content block --- templates/default/layout.html.twig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 3018a31..8fc70a0 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -13,7 +13,7 @@
- {% block header_search %} @@ -36,9 +36,7 @@ {route : app.request.get('_route')} )) }} {% endblock %} -
- {% block main_content %}{% endblock %} -
+ {% block main_content %}{% endblock %}
From 36329b0b81343f46643d5e08bcbc7e685b43c216 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 17:47:36 +0300 Subject: [PATCH 101/434] update paddings --- templates/default/layout.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 8fc70a0..837a2eb 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -12,7 +12,7 @@ {% block header %}
-
+
From fbeb793f6db383620a9ba6ddb26a53d917bee96b Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 19:02:25 +0300 Subject: [PATCH 102/434] move containers inside the main_content block --- templates/default/user/info.html.twig | 152 ++++++++--------- templates/default/user/profile.html.twig | 202 ++++++++++++----------- 2 files changed, 179 insertions(+), 175 deletions(-) diff --git a/templates/default/user/info.html.twig b/templates/default/user/info.html.twig index d837cd5..9d3df05 100644 --- a/templates/default/user/info.html.twig +++ b/templates/default/user/info.html.twig @@ -1,88 +1,90 @@ {% extends 'default/layout.html.twig' %} {% block title %}{{ 'User'|trans }} #{{ user.id }} - {{ name }}{% endblock %} {% block main_content %} -
- {{ 'identicon'|trans }} -
-

{{ 'Profile'|trans }}

-
- - - - - {% if user.address %} +
+
+ {{ 'identicon'|trans }} +
+

{{ 'Profile'|trans }}

+
{{ 'Common'|trans }}
+ - - + + {% if user.address %} + + + + + + + + + {% else %} + + + + + {% endif %} + + + + + + - - + + - {% else %} - - + + - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'Address'|trans }} - {{ user.address }} - - - - + {{ 'Common'|trans }}
{{ 'Address'|trans }} + {{ user.address }} - + - + + + + + +
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Access'|trans }}
{{ 'Status'|trans }} + {% if user.status %} + {{ 'active'|trans }} + {% else %} + {{ 'disabled'|trans }} + {% endif %}
{{ 'Joined'|trans }}{{ user.added }}{{ 'Approved'|trans }} + {% if user.approved %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Joined'|trans }}{{ user.added }}{{ 'Moderator'|trans }} + {% if user.moderator %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Access'|trans }}
{{ 'Status'|trans }} - {% if user.status %} - {{ 'active'|trans }} - {% else %} - {{ 'disabled'|trans }} - {% endif %} -
{{ 'Approved'|trans }} - {% if user.approved %} - {{ 'yes'|trans }} - {% else %} - {{ 'no'|trans }} - {% endif %} -
{{ 'Moderator'|trans }} - {% if user.moderator %} - {{ 'yes'|trans }} - {% else %} - {{ 'no'|trans }} - {% endif %} -
{{ 'Settings'|trans }}
{{ 'Interface'|trans }} - {{ user.locale|locale_name(user.locale)|u.title }} -
{{ 'Content filter'|trans }} - {% for i, locale in user.locales %}{% if i > 0 %},{% endif %} {{ locale|locale_name(locale)|u.title }}{% endfor %} -
+ + {{ 'Settings'|trans }} + + + {{ 'Interface'|trans }} + + {{ user.locale|locale_name(user.locale)|u.title }} + + + + {{ 'Content filter'|trans }} + + {% for i, locale in user.locales %}{% if i > 0 %},{% endif %} {{ locale|locale_name(locale)|u.title }}{% endfor %} + + + + +
{% endblock %} \ No newline at end of file diff --git a/templates/default/user/profile.html.twig b/templates/default/user/profile.html.twig index 3f9909f..997b3fa 100644 --- a/templates/default/user/profile.html.twig +++ b/templates/default/user/profile.html.twig @@ -1,109 +1,111 @@ {% extends 'default/layout.html.twig' %} {% block title %}{{ 'Profile'|trans }} - {{ name }}{% endblock %} {% block main_content %} - -
- {{ 'identicon'|trans }} -
-

{{ 'Profile'|trans }}

- - - - - - - - + + +
{{ 'Common'|trans }}
{{ 'Address'|trans }} - {{ user.address }} - - - - +
+ +
+ {{ 'identicon'|trans }} +
+

{{ 'Profile'|trans }}

+ + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - -
{{ 'Common'|trans }}
{{ 'Address'|trans }} + {{ user.address }} - + - -
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Access'|trans }}
{{ 'Status'|trans }} - {% if user.status %} - {{ 'active'|trans }} - {% else %} - {{ 'disabled'|trans }} - {% endif %} -
{{ 'Approved'|trans }} - {% if user.approved %} - {{ 'yes'|trans }} - {% else %} - {{ 'no'|trans }} - {% endif %} -
{{ 'Moderator'|trans }} - {% if user.moderator %} - {{ 'yes'|trans }} - {% else %} - {{ 'no'|trans }} - {% endif %} -
{{ 'Settings'|trans }}
{{ 'Interface'|trans }} -
{{ 'Joined'|trans }}{{ user.added }}
{{ 'Access'|trans }}
{{ 'Status'|trans }} + {% if user.status %} + {{ 'active'|trans }} + {% else %} + {{ 'disabled'|trans }} + {% endif %} +
{{ 'Approved'|trans }} + {% if user.approved %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Moderator'|trans }} + {% if user.moderator %} + {{ 'yes'|trans }} + {% else %} + {{ 'no'|trans }} + {% endif %} +
{{ 'Settings'|trans }}
{{ 'Interface'|trans }} + +
{{ 'Content filter'|trans }} {% for locale in locales %} - {% if locale == user.locale %} -
{{ 'Content filter'|trans }} - {% for locale in locales %} -
- {% if locale in user.locales %} - - {% else %} - - {% endif %} - -
- {% endfor %} -
-
- -
- +
+
+ +
+ +
{% endblock %} \ No newline at end of file From 86807187143adc4c2beeff68447cdb816576952b Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 19:15:17 +0300 Subject: [PATCH 103/434] add more languages, init crowdin driver #19 --- .env | 2 +- config/packages/translation.yaml | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.env b/.env index d49f3c3..34c0871 100644 --- a/.env +++ b/.env @@ -50,4 +50,4 @@ APP_VERSION='2.0.0' APP_NAME=YGGtracker APP_LOCALE=en -APP_LOCALES=de|fr|en|uk \ No newline at end of file +APP_LOCALES=de|fr|en|uk diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index 888f0ba..89f3399 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -4,9 +4,11 @@ framework: default_path: '%kernel.project_dir%/translations' fallbacks: - en -# providers: -# crowdin: -# dsn: '%env(CROWDIN_DSN)%' + providers: + crowdin: + dsn: '%env(CROWDIN_DSN)%' + domains: ['messages'] + locales: ['fr', 'de', 'en', 'uk'] # loco: # dsn: '%env(LOCO_DSN)%' # lokalise: From 02e56e4d081d4b8a862c845ef82be8ef58fd52df Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 19:15:33 +0300 Subject: [PATCH 104/434] decrease container width --- public/asset/default/css/framework.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/asset/default/css/framework.css b/public/asset/default/css/framework.css index aa38b76..d06892a 100644 --- a/public/asset/default/css/framework.css +++ b/public/asset/default/css/framework.css @@ -1,7 +1,7 @@ .container { position: relative; overflow: hidden; - max-width: 748px; + max-width: 680px; margin: 0 auto; } From 8ee25d3a301b38b3db5e04a32715f29335ff8244 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 19:17:43 +0300 Subject: [PATCH 105/434] move dashboard relations to the user controller --- src/Controller/DashboardController.php | 43 ---------------- src/Controller/UserController.php | 56 +++++++++++++++++++++ templates/default/dashboard/index.html.twig | 2 - templates/default/layout.html.twig | 2 +- templates/default/user/module.html.twig | 4 +- 5 files changed, 59 insertions(+), 48 deletions(-) delete mode 100644 src/Controller/DashboardController.php delete mode 100644 templates/default/dashboard/index.html.twig diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php deleted file mode 100644 index 817fd3b..0000000 --- a/src/Controller/DashboardController.php +++ /dev/null @@ -1,43 +0,0 @@ -init( - $request->getClientIp() - ); - - return $this->redirectToRoute( - 'dashboard_index', - [ - '_locale' => $user->getLocale() - ] - ); - } - - #[Route( - '/{_locale}', - name: 'dashboard_index' - )] - public function index(Request $request): Response - { - return $this->render( - 'default/dashboard/index.html.twig' - ); - } -} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index af88166..8b9b5be 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -12,6 +12,62 @@ use App\Service\TimeService; class UserController extends AbstractController { + #[Route('/')] + public function root( + Request $request, + UserService $userService + ): Response + { + $user = $userService->init( + $request->getClientIp() + ); + + return $this->redirectToRoute( + 'user_dashboard', + [ + '_locale' => $user->getLocale() + ] + ); + } + + #[Route( + '/{_locale}', + name: 'user_dashboard' + )] + public function index( + Request $request, + UserService $userService, + TimeService $timeService + ): Response + { + $activities = []; + foreach ($userService->getAllByAddedFieldDesc() as $user) + { + $activities[] = + [ + 'user' => + [ + 'id' => $user->getId(), + 'identicon' => $userService->identicon( + $user->getAddress(), + 24 + ) + ], + 'type' => 'join', + 'added' => $timeService->ago( + $user->getAdded() + ) + ]; + } + + return $this->render( + 'default/user/dashboard.html.twig', + [ + 'activities' => $activities + ] + ); + } + #[Route( '/{_locale}/profile', name: 'user_profile', diff --git a/templates/default/dashboard/index.html.twig b/templates/default/dashboard/index.html.twig deleted file mode 100644 index fe403d6..0000000 --- a/templates/default/dashboard/index.html.twig +++ /dev/null @@ -1,2 +0,0 @@ -{% extends 'default/layout.html.twig' %} -{% block title %}{{ 'Home'|trans }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 837a2eb..159563a 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -13,7 +13,7 @@
- {% block header_search %} diff --git a/templates/default/user/module.html.twig b/templates/default/user/module.html.twig index 366eb66..1dbd40f 100644 --- a/templates/default/user/module.html.twig +++ b/templates/default/user/module.html.twig @@ -1,6 +1,6 @@
- {% if route == 'dashboard_index' %} + {% if route == 'user_dashboard' %} @@ -10,7 +10,7 @@ {% else %} - + From d73086231fb5cfd6866d8520bad94e96351a02af Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 19:20:34 +0300 Subject: [PATCH 106/434] draft activity feature #4 --- src/Controller/UserController.php | 34 +++++----------------- src/Repository/UserRepository.php | 9 ++++++ src/Service/UserService.php | 25 ++++++++++++++++ templates/default/user/dashboard.html.twig | 23 +++++++++++++++ 4 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 templates/default/user/dashboard.html.twig diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 8b9b5be..2b543bc 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -129,18 +129,6 @@ class UserController extends AbstractController ); } - // Generate identicon - $identicon = new \Jdenticon\Identicon(); - - $identicon->setValue($user->getAddress()); - $identicon->setSize(48); - $identicon->setStyle( - [ - 'backgroundColor' => 'rgba(255, 255, 255, 0)', - 'padding' => 0 - ] - ); - // Render template return $this->render( 'default/user/profile.html.twig', @@ -156,7 +144,10 @@ class UserController extends AbstractController 'added' => $timeService->ago( $user->getAdded() ), - 'identicon' => $identicon->getImageDataUri('webp'), + 'identicon' => $userService->identicon( + $user->getAddress(), + 48 + ), ], 'locales' => explode('|', $this->getParameter('app.locales')) ] @@ -185,18 +176,6 @@ class UserController extends AbstractController throw $this->createNotFoundException(); } - // Generate identicon - $identicon = new \Jdenticon\Identicon(); - - $identicon->setValue($user->getAddress()); - $identicon->setSize(48); - $identicon->setStyle( - [ - 'backgroundColor' => 'rgba(255, 255, 255, 0)', - 'padding' => 0 - ] - ); - // Render template return $this->render( 'default/user/info.html.twig', @@ -212,7 +191,10 @@ class UserController extends AbstractController 'added' => $timeService->ago( $user->getAdded() ), - 'identicon' => $identicon->getImageDataUri('webp'), + 'identicon' => $userService->identicon( + $user->getAddress(), + 48 + ), ] ] ); diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 49896f3..202d56d 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -40,4 +40,13 @@ class UserRepository extends ServiceEntityRepository ->getOneOrNullResult() ; } + + public function findAllByAddedFieldDesc(): array + { + return $this->createQueryBuilder('u') + ->orderBy('u.added', 'DESC') + ->getQuery() + ->getResult() + ; + } } diff --git a/src/Service/UserService.php b/src/Service/UserService.php index 856fa9e..033c459 100644 --- a/src/Service/UserService.php +++ b/src/Service/UserService.php @@ -58,6 +58,31 @@ class UserService return $this->userRepository->findOneByIdField($id); } + public function getAllByAddedFieldDesc(): array + { + return $this->userRepository->findAllByAddedFieldDesc(); + } + + public function identicon( + mixed $value, + int $size = 16, + array $style = + [ + 'backgroundColor' => 'rgba(255, 255, 255, 0)', + 'padding' => 0 + ], + string $format = 'webp' + ): string + { + $identicon = new \Jdenticon\Identicon(); + + $identicon->setValue($value); + $identicon->setSize($size); + $identicon->setStyle($style); + + return $identicon->getImageDataUri($format); + } + public function save(User $user) : void { $this->entityManager->persist($user); diff --git a/templates/default/user/dashboard.html.twig b/templates/default/user/dashboard.html.twig new file mode 100644 index 0000000..0fa8921 --- /dev/null +++ b/templates/default/user/dashboard.html.twig @@ -0,0 +1,23 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ 'Last activity'|trans }} - {{ name }}{% endblock %} +{% block main_content %} + {% for activity in activities %} +
+
+
+ + {{ 'identicon'|trans }} + + {% if activity.type == 'join' %} + {{ 'joined'|trans }} {{ name }} + {% endif %} +
+
+ {{ activity.added }} +
+
+
+ {% endfor %} +{% endblock %} \ No newline at end of file From b9a2804132757a01d86a0f3490daa3fe4936ec8f Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 19:22:59 +0300 Subject: [PATCH 107/434] remove deprecated methods --- src/Controller/PageController.php | 45 ------------------------- templates/default/user/module.html.twig | 2 ++ 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index 8499718..fe48db3 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -24,49 +24,4 @@ class PageController extends AbstractController // @TODO ]); } - - #[Route( - '/{_locale}/page/stars', - name: 'page_stars' - )] - public function stars(): Response - { - // @TODO - } - - #[Route( - '/{_locale}/page/views', - name: 'page_views' - )] - public function views(): Response - { - // @TODO - } - - #[Route( - '/{_locale}/page/downloads', - name: 'page_downloads' - )] - public function downloads(): Response - { - // @TODO - } - - #[Route( - '/{_locale}/page/comments', - name: 'page_comments' - )] - public function comments(): Response - { - // @TODO - } - - #[Route( - '/{_locale}/page/editions', - name: 'page_editions' - )] - public function editions(): Response - { - // @TODO - } } \ No newline at end of file diff --git a/templates/default/user/module.html.twig b/templates/default/user/module.html.twig index 1dbd40f..f64cd78 100644 --- a/templates/default/user/module.html.twig +++ b/templates/default/user/module.html.twig @@ -38,6 +38,7 @@ {% endif %} + {# {% if route == 'page_stars' %} @@ -163,6 +164,7 @@ {% endif %} + #} {% if route == 'page_submit' %} From 5f5da0ded45892f5986725fd7a5b13af2939297e Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 20:49:52 +0300 Subject: [PATCH 108/434] add app version to css --- templates/default/layout.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 159563a..ef4ee5b 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -4,8 +4,8 @@ {% block title %}{{ name }}{% endblock %} {% block stylesheets %} - - + + {% endblock %} From 2d6fa0508139d4fe465a357245ea21e5a7bf96a3 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 20:50:02 +0300 Subject: [PATCH 109/434] increase textarea size --- public/asset/default/css/common.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/asset/default/css/common.css b/public/asset/default/css/common.css index cb5d2ff..f7a537f 100644 --- a/public/asset/default/css/common.css +++ b/public/asset/default/css/common.css @@ -65,7 +65,7 @@ input:focus { } textarea { - min-height: 86px; + min-height: 160px; } /* @TODO improve focus out From 1fffab732c9bed19f5aec3b6365ad1c44cac701a Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 22:09:18 +0300 Subject: [PATCH 110/434] remove address from profile info --- templates/default/user/info.html.twig | 29 ++++----------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/templates/default/user/info.html.twig b/templates/default/user/info.html.twig index 9d3df05..4c31c87 100644 --- a/templates/default/user/info.html.twig +++ b/templates/default/user/info.html.twig @@ -11,31 +11,10 @@ {{ 'Common'|trans }} - {% if user.address %} - - {{ 'Address'|trans }} - - {{ user.address }} - - - - - - - - - - - - {{ 'Joined'|trans }} - {{ user.added }} - - {% else %} - - {{ 'Joined'|trans }} - {{ user.added }} - - {% endif %} + + {{ 'Joined'|trans }} + {{ user.added }} + {{ 'Access'|trans }} From 82a4860e0c7c14a4be01855e83736385ab09381f Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 4 Oct 2023 23:12:59 +0300 Subject: [PATCH 111/434] remove translation link --- templates/default/layout.html.twig | 2 -- 1 file changed, 2 deletions(-) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index ef4ee5b..12c3d73 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -48,8 +48,6 @@
{% block footer_trackers %}{% endblock %} - {{'Translate'|trans }} - | GitHub
From ffa568275fef00224dc196e3ffddccdc3d5b3c56 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 00:12:48 +0300 Subject: [PATCH 112/434] init user session on dashboard page --- src/Controller/UserController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 2b543bc..6c867f4 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -40,6 +40,12 @@ class UserController extends AbstractController TimeService $timeService ): Response { + // Init user session + $userService->init( + $request->getClientIp() + ); + + // Build activity history $activities = []; foreach ($userService->getAllByAddedFieldDesc() as $user) { From 731624d886b5adaf37100081348cd08abf246609 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 00:15:00 +0300 Subject: [PATCH 113/434] draft submit form #14 --- .env | 8 + config/services.yaml | 8 + src/Controller/PageController.php | 185 ++++++++++++++++++- src/Service/PageService.php | 37 ++++ templates/default/page/submit.html.twig | 232 +++++++++++------------- 5 files changed, 340 insertions(+), 130 deletions(-) create mode 100644 src/Service/PageService.php diff --git a/.env b/.env index 34c0871..de0c6aa 100644 --- a/.env +++ b/.env @@ -51,3 +51,11 @@ APP_NAME=YGGtracker APP_LOCALE=en APP_LOCALES=de|fr|en|uk + +APP_PAGE_TITLE_LENGTH_MIN=10 +APP_PAGE_TITLE_LENGTH_MAX=255 +APP_PAGE_DESCRIPTION_LENGTH_MIN=0 +APP_PAGE_DESCRIPTION_LENGTH_MAX=10000 +APP_PAGE_TORRENT_QUANTITY_MIN=1 +APP_PAGE_TORRENT_QUANTITY_MAX=100 +APP_PAGE_TORRENT_SIZE_MAX=1024000 \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index df414d4..1bccbb7 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -8,6 +8,14 @@ parameters: app.name: '%env(APP_NAME)%' app.locale: '%env(APP_LOCALE)%' app.locales: '%env(APP_LOCALES)%' + app.page.title.length.min: '%env(APP_PAGE_TITLE_LENGTH_MIN)%' + app.page.title.length.max: '%env(APP_PAGE_TITLE_LENGTH_MAX)%' + app.page.description.length.min: '%env(APP_PAGE_DESCRIPTION_LENGTH_MIN)%' + app.page.description.length.max: '%env(APP_PAGE_DESCRIPTION_LENGTH_MAX)%' + app.page.torrent.quantity.min: '%env(APP_PAGE_TORRENT_QUANTITY_MIN)%' + app.page.torrent.quantity.max: '%env(APP_PAGE_TORRENT_QUANTITY_MAX)%' + app.page.torrent.size.min: '%env(APP_PAGE_TORRENT_SIZE_MIN)%' + app.page.torrent.size.max: '%env(APP_PAGE_TORRENT_SIZE_MAX)%' services: # default configuration for services in *this* file diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index fe48db3..1a580b2 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -3,25 +3,196 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Contracts\Translation\TranslatorInterface; + use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; +use App\Service\UserService; +use App\Service\PageService; +use App\Service\TimeService; + class PageController extends AbstractController { #[Route( '/{_locale}/page/submit', name: 'page_submit' )] - public function submit(): Response + public function submit( + Request $request, + TranslatorInterface $translator, + UserService $userService, + PageService $pageService, + ): Response { - /* - return $this->redirectToRoute('page', [ - 'id' => $page->getId() - ]); - */ - return $this->render('default/page/submit.html.twig', [ + // Init user + $user = $userService->init( + $request->getClientIp() + ); + + if (!$user->isStatus()) + { // @TODO + throw new \Exception( + $translator->trans('Access denied') + ); + } + + // Init form + $form = + [ + 'locale' => + [ + 'error' => [], + 'value' => $request->get('_locale'), + 'placeholder' => $translator->trans('Content language'), + ], + 'title' => + [ + 'error' => [], + 'attribute' => + [ + 'value' => $request->get('title'), + 'minlength' => $this->getParameter('app.page.title.length.min'), + 'maxlength' => $this->getParameter('app.page.title.length.max'), + 'placeholder' => sprintf( + $translator->trans('Page title text (%s-%s chars)'), + number_format($this->getParameter('app.page.title.length.min')), + number_format($this->getParameter('app.page.title.length.max')) + ), + ] + ], + 'description' => + [ + 'error' => [], + 'attribute' => + [ + 'value' => $request->get('description'), + 'minlength' => $this->getParameter('app.page.description.length.min'), + 'maxlength' => $this->getParameter('app.page.description.length.max'), + 'placeholder' => sprintf( + $translator->trans('Page description text (%s-%s chars)'), + number_format($this->getParameter('app.page.description.length.min')), + number_format($this->getParameter('app.page.description.length.max')) + ), + ] + ], + 'torrents' => + [ + 'error' => [], + 'attribute' => + [ + 'placeholder' => $translator->trans('Select torrent files'), + ] + ], + 'sensitive' => + [ + 'error' => [], + 'attribute' => + [ + 'value' => $request->get('sensitive'), + 'placeholder' => $translator->trans('Apply sensitive filters for this publication'), + ] + ] + ]; + + // Process request + if ($request->isMethod('post')) + { + // Init new + $page = $pageService->new(); + + /// Locale + if (!in_array($request->get('locale'), explode('|', $this->getParameter('app.locales')))) + { + $form['locale']['error'][] = $translator->trans('Requested locale not supported'); + } + + else + { + // $request->get('locale') + } + + /// Title + if (mb_strlen($request->get('title')) < $this->getParameter('app.page.title.length.min') || + mb_strlen($request->get('title')) > $this->getParameter('app.page.title.length.max')) + { + $form['title']['error'][] = sprintf( + $translator->trans('Page title out of %s-%s chars'), + number_format($this->getParameter('app.page.title.length.min')), + number_format($this->getParameter('app.page.title.length.max')) + ); + } + + else + { + // $request->get('title') + } + + /// Description + if (mb_strlen($request->get('description')) < $this->getParameter('app.page.description.length.min') || + mb_strlen($request->get('description')) > $this->getParameter('app.page.description.length.max')) + { + $form['description']['error'][] = sprintf( + $translator->trans('Page description out of %s-%s chars'), + number_format($this->getParameter('app.page.description.length.min')), + number_format($this->getParameter('app.page.description.length.max')) + ); + } + + else + { + // $request->get('description') + } + + /// Torrents + $total = 0; + + if ($files = $request->files->get('torrents')) + { + foreach ($files as $file) + { + //// Quantity + $total++; + + //// File size + if (filesize($file->getPathName()) > $this->getParameter('app.page.torrent.size.max')) + { + $form['torrents']['error'][] = $translator->trans('Torrent file out of size limit'); + } + + //// Content + $decoder = new \BitTorrent\Decoder(); + $decodedFile = $decoder->decodeFile( + $file->getPathName() + ); + + // var_dump($decodedFile['info']['name']); + } + } + + if ($total < $this->getParameter('app.page.torrent.quantity.min') || + $total > $this->getParameter('app.page.torrent.quantity.max')) + { + $form['torrents']['error'][] = sprintf( + $translator->trans('Torrents quantity out of %s-%s range'), + number_format($this->getParameter('app.page.torrent.quantity.min')), + number_format($this->getParameter('app.page.torrent.quantity.max')) + ); + } + + + if (empty($error)) + { + // isset($request->get('sensitive')) + // $pageService->save($page); + } + } + + return $this->render('default/page/submit.html.twig', [ + 'locales' => explode('|', $this->getParameter('app.locales')), + 'form' => $form, ]); } } \ No newline at end of file diff --git a/src/Service/PageService.php b/src/Service/PageService.php new file mode 100644 index 0000000..ae50c44 --- /dev/null +++ b/src/Service/PageService.php @@ -0,0 +1,37 @@ +entityManager = $entityManager; + $this->pageRepository = $entityManager->getRepository(Page::class); + $this->parameterBagInterface = $parameterBagInterface; + } + + public function new(): ?Page + { + return new Page(); + } + + public function save(Page $page) : void + { + $this->entityManager->persist($page); + $this->entityManager->flush(); + } +} \ No newline at end of file diff --git a/templates/default/page/submit.html.twig b/templates/default/page/submit.html.twig index 736ec81..19d4cb6 100644 --- a/templates/default/page/submit.html.twig +++ b/templates/default/page/submit.html.twig @@ -1,129 +1,115 @@ {% extends 'default/layout.html.twig' %} {% block title %}{{'Submit'|trans }} - {{ name }}{% endblock %} - {% block main_content %} -
+
+

{{'Submit'|trans }}

+
+
+
+ + + + + + + +
+
+ + + + + + + {% for error in form.title.error %} +
+ {{ error }} +
+ {% endfor %} + +
+
+ + + + + + + {% for error in form.description.error %} +
+ {{ error }} +
+ {% endfor %} + +
+
+ + + + + + + {% for error in form.torrents.error %} +
+ {{ error }} +
+ {% endfor %} + +
+
+ + + + + + + +
+
+ +
+
-
- -
- - - - - - - -
-
- - - - - - - {% for errors in form.title.error %} - {% for error in errors %} -
- {{ error }} -
- {% endfor %} - {% endfor %} - -
-
- - - - - - - {% for errors in form.description.error %} - {% for error in errors %} -
- {{ error }} -
- {% endfor %} - {% endfor %} - -
-
- - - - - - - {% for errors in form.keywords.error %} - {% for error in errors %} -
- {{ error }} -
- {% endfor %} - {% endfor %} - -
-
- - - - - - - -
-
- -
-
{% endblock %} From fe7e33f7eff7d9b042dd7df340453d711fe66d6f Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 14:30:05 +0300 Subject: [PATCH 114/434] update translation strings #19 --- translations/messages+intl-icu.uk.xlf | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/translations/messages+intl-icu.uk.xlf b/translations/messages+intl-icu.uk.xlf index c0a45c2..c39ab02 100644 --- a/translations/messages+intl-icu.uk.xlf +++ b/translations/messages+intl-icu.uk.xlf @@ -173,6 +173,78 @@ Save Save + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + From f6e6a0f114d114ba1837409552aa786894b20296 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 14:51:39 +0300 Subject: [PATCH 115/434] add Crowdin link #19 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d833dc2..da84fa3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ YGGtracker uses [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) I * `php bin/console doctrine:schema:update --force` * `symfony server:start` +#### Translation + +* [Crowdin](https://crowdin.com/project/yggtracker) + #### Contribution Please make new branch for each PR From 12e56bf6d13238dfe49fbb2fa8e68951df0097e8 Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 14:53:20 +0300 Subject: [PATCH 116/434] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da84fa3..7684e8c 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ YGGtracker uses [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) I * `php bin/console doctrine:schema:update --force` * `symfony server:start` -#### Translation +#### Localization -* [Crowdin](https://crowdin.com/project/yggtracker) +* Community translations by [Crowdin](https://crowdin.com/project/yggtracker) #### Contribution From eade8260d76438d633b3d2987c33dc8f21311dda Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 15:04:55 +0300 Subject: [PATCH 117/434] update translation #19 --- translations/messages+intl-icu.uk.xlf | 244 ++++++++++++-------------- 1 file changed, 110 insertions(+), 134 deletions(-) diff --git a/translations/messages+intl-icu.uk.xlf b/translations/messages+intl-icu.uk.xlf index c39ab02..6c5b0ef 100644 --- a/translations/messages+intl-icu.uk.xlf +++ b/translations/messages+intl-icu.uk.xlf @@ -7,171 +7,59 @@ now - now + зараз year - year + рік month - month + місяць day - day + день hour - hour + година minute - minute + хвилина second - second + секунда years - years + роки months - months + місяці days - days + дні hours - hours + години minutes - minutes + хвилини seconds - seconds + секунди ago - ago - - - Translate - Translate - - - Home - Home - - - Search - Search - - - Keyword, file, extension, hash... - Keyword, file, extension, hash... - - - Profile - Profile - - - Stars - Stars - - - Views - Views - - - Comments - Comments - - - Downloads - Downloads - - - Editions - Editions - - - Submit - Submit - - - User - User - - - Common - Common - - - Address - Address - - - Address hidden for others - Address hidden for others - - - Joined - Joined - - - Access - Access - - - Status - Status - - - active - active - - - disabled - disabled - - - Approved - Approved - - - yes - yes - - - no - no - - - Moderator - Moderator - - - Settings - Settings - - - Interface - Interface - - - Content filter - Content filter - - - Save - Save + тому Access denied @@ -179,7 +67,7 @@ Content language - Content language + Мова контенту Page title text (%s-%s chars) @@ -217,33 +105,121 @@ Torrents quantity out of %s-%s range Torrents quantity out of %s-%s range + + Submit + Надіслати + Title - Title + Заголовок Description - Description + Опис Torrent files - Torrent files + Файли торентів Sensitive - Sensitive + Чутливий вміст + + + Search + Пошук + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... Last activity - Last activity + Остання активність identicon - identicon + піктограма joined - joined + приєднався + + + Home + Головна + + + Profile + Профіль + + + User + Користувач + + + Common + Загальні + + + Joined + Приєднався + + + Access + Доступ + + + Status + Статус + + + active + активний + + + disabled + вимкнено + + + Approved + Затверджений + + + yes + так + + + no + ні + + + Moderator + Модератор + + + Settings + Налаштування + + + Interface + Інтерфейс + + + Content filter + Фільтр вмісту + + + Address + Адреса + + + Address hidden for others + Адресу приховано для інших + + + Save + Зберегти From edc78531d30a841ab7ac3a7981b0994ec27df1ae Mon Sep 17 00:00:00 2001 From: ghost Date: Thu, 5 Oct 2023 15:32:12 +0300 Subject: [PATCH 118/434] add new locales #19 --- .env | 2 +- config/packages/translation.yaml | 2 +- translations/messages+intl-icu.cs.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.de.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.en.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.eo.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.es.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.fr.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.he.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.it.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.ka.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.lv.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.pl.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.pt.xlf | 226 ++++++++++++++++++++++++++ translations/messages+intl-icu.ru.xlf | 226 ++++++++++++++++++++++++++ 15 files changed, 2940 insertions(+), 2 deletions(-) create mode 100644 translations/messages+intl-icu.cs.xlf create mode 100644 translations/messages+intl-icu.de.xlf create mode 100644 translations/messages+intl-icu.en.xlf create mode 100644 translations/messages+intl-icu.eo.xlf create mode 100644 translations/messages+intl-icu.es.xlf create mode 100644 translations/messages+intl-icu.fr.xlf create mode 100644 translations/messages+intl-icu.he.xlf create mode 100644 translations/messages+intl-icu.it.xlf create mode 100644 translations/messages+intl-icu.ka.xlf create mode 100644 translations/messages+intl-icu.lv.xlf create mode 100644 translations/messages+intl-icu.pl.xlf create mode 100644 translations/messages+intl-icu.pt.xlf create mode 100644 translations/messages+intl-icu.ru.xlf diff --git a/.env b/.env index de0c6aa..284b058 100644 --- a/.env +++ b/.env @@ -50,7 +50,7 @@ APP_VERSION='2.0.0' APP_NAME=YGGtracker APP_LOCALE=en -APP_LOCALES=de|fr|en|uk +APP_LOCALES=en|cs|eo|fr|ka|de|he|it|lv|pl|pt|ru|es|uk APP_PAGE_TITLE_LENGTH_MIN=10 APP_PAGE_TITLE_LENGTH_MAX=255 diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index 89f3399..bd2fce8 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -8,7 +8,7 @@ framework: crowdin: dsn: '%env(CROWDIN_DSN)%' domains: ['messages'] - locales: ['fr', 'de', 'en', 'uk'] + locales: ['en','cs','eo','fr','ka','de','he','it','lv','pl','pt','ru','es','uk'] # loco: # dsn: '%env(LOCO_DSN)%' # lokalise: diff --git a/translations/messages+intl-icu.cs.xlf b/translations/messages+intl-icu.cs.xlf new file mode 100644 index 0000000..b576615 --- /dev/null +++ b/translations/messages+intl-icu.cs.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.de.xlf b/translations/messages+intl-icu.de.xlf new file mode 100644 index 0000000..7d56b66 --- /dev/null +++ b/translations/messages+intl-icu.de.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.en.xlf b/translations/messages+intl-icu.en.xlf new file mode 100644 index 0000000..6ac8828 --- /dev/null +++ b/translations/messages+intl-icu.en.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.eo.xlf b/translations/messages+intl-icu.eo.xlf new file mode 100644 index 0000000..3ca70e3 --- /dev/null +++ b/translations/messages+intl-icu.eo.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.es.xlf b/translations/messages+intl-icu.es.xlf new file mode 100644 index 0000000..f81b4e9 --- /dev/null +++ b/translations/messages+intl-icu.es.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.fr.xlf b/translations/messages+intl-icu.fr.xlf new file mode 100644 index 0000000..20a14eb --- /dev/null +++ b/translations/messages+intl-icu.fr.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.he.xlf b/translations/messages+intl-icu.he.xlf new file mode 100644 index 0000000..4ce0aa0 --- /dev/null +++ b/translations/messages+intl-icu.he.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.it.xlf b/translations/messages+intl-icu.it.xlf new file mode 100644 index 0000000..11eaea4 --- /dev/null +++ b/translations/messages+intl-icu.it.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.ka.xlf b/translations/messages+intl-icu.ka.xlf new file mode 100644 index 0000000..6656860 --- /dev/null +++ b/translations/messages+intl-icu.ka.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.lv.xlf b/translations/messages+intl-icu.lv.xlf new file mode 100644 index 0000000..8a48704 --- /dev/null +++ b/translations/messages+intl-icu.lv.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.pl.xlf b/translations/messages+intl-icu.pl.xlf new file mode 100644 index 0000000..2a60196 --- /dev/null +++ b/translations/messages+intl-icu.pl.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.pt.xlf b/translations/messages+intl-icu.pt.xlf new file mode 100644 index 0000000..5d0f695 --- /dev/null +++ b/translations/messages+intl-icu.pt.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
diff --git a/translations/messages+intl-icu.ru.xlf b/translations/messages+intl-icu.ru.xlf new file mode 100644 index 0000000..fb9a03f --- /dev/null +++ b/translations/messages+intl-icu.ru.xlf @@ -0,0 +1,226 @@ + + + +
+ +
+ + + now + now + + + year + year + + + month + month + + + day + day + + + hour + hour + + + minute + minute + + + second + second + + + years + years + + + months + months + + + days + days + + + hours + hours + + + minutes + minutes + + + seconds + seconds + + + ago + ago + + + Access denied + Access denied + + + Content language + Content language + + + Page title text (%s-%s chars) + Page title text (%s-%s chars) + + + Page description text (%s-%s chars) + Page description text (%s-%s chars) + + + Select torrent files + Select torrent files + + + Apply sensitive filters for this publication + Apply sensitive filters for this publication + + + Requested locale not supported + Requested locale not supported + + + Page title out of %s-%s chars + Page title out of %s-%s chars + + + Page description out of %s-%s chars + Page description out of %s-%s chars + + + Torrent file out of size limit + Torrent file out of size limit + + + Torrents quantity out of %s-%s range + Torrents quantity out of %s-%s range + + + Submit + Submit + + + Title + Title + + + Description + Description + + + Torrent files + Torrent files + + + Sensitive + Sensitive + + + Search + Search + + + Keyword, file, extension, hash... + Keyword, file, extension, hash... + + + Last activity + Last activity + + + identicon + identicon + + + joined + joined + + + Home + Home + + + Profile + Profile + + + User + User + + + Common + Common + + + Joined + Joined + + + Access + Access + + + Status + Status + + + active + active + + + disabled + disabled + + + Approved + Approved + + + yes + yes + + + no + no + + + Moderator + Moderator + + + Settings + Settings + + + Interface + Interface + + + Content filter + Content filter + + + Address + Address + + + Address hidden for others + Address hidden for others + + + Save + Save + + +
+
From b1fe274ae8f72290eeb1c9f8174b8406b45ee2a3 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 6 Oct 2023 00:48:57 +0300 Subject: [PATCH 119/434] add paddings --- templates/default/user/module.html.twig | 240 ++++++++++++------------ 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/templates/default/user/module.html.twig b/templates/default/user/module.html.twig index f64cd78..2e3604f 100644 --- a/templates/default/user/module.html.twig +++ b/templates/default/user/module.html.twig @@ -40,149 +40,149 @@ {% endif %} {# {% if route == 'page_stars' %} - - - - - - {{ 'Stars'|trans }} + + + + + + {{ 'Stars'|trans }} + + + {{ stars }} + - - {{ stars }} - - {% else %} - - - - - - {{ 'Stars'|trans }} - - - {{ stars }} - - + + + + + + {{ 'Stars'|trans }} + + + {{ stars }} + + {% endif %} {% if route == 'page_views' %} - - - - - - {{ 'Views'|trans }} + + + + + + {{ 'Views'|trans }} + + + {{ views }} + - - {{ views }} - - {% else %} - - - - - - {{ 'Views'|trans }} - - - {{ views }} - - + + + + + + {{ 'Views'|trans }} + + + {{ views }} + + {% endif %} {% if route == 'page_comments' %} - - - - - - {{ 'Comments'|trans }} + + + + + + {{ 'Comments'|trans }} + + + {{ comments }} + - - {{ comments }} - - {% else %} - - - - - - {{ 'Comments'|trans }} - - - {{ comments }} - - + + + + + + {{ 'Comments'|trans }} + + + {{ comments }} + + {% endif %} {% if route == 'page_downloads' %} - - - - - - {{ 'Downloads'|trans }} + + + + + + {{ 'Downloads'|trans }} + + + {{ downloads }} + - - {{ downloads }} - - {% else %} - - - - - - {{ 'Downloads'|trans }} - - - {{ downloads }} - - + + + + + + {{ 'Downloads'|trans }} + + + {{ downloads }} + + {% endif %} {% if route == 'page_editions' %} - - - - - - {{ 'Editions'|trans }} + + + + + + {{ 'Editions'|trans }} + + + {{ editions }} + - - {{ editions }} - - {% else %} - - - - - - {{ 'Editions'|trans }} - - - {{ editions }} - - + + + + + + {{ 'Editions'|trans }} + + + {{ editions }} + + {% endif %} #} {% if route == 'page_submit' %} - - - - - - {{ 'Submit'|trans }} + + + + + + {{ 'Submit'|trans }} + - {% else %} - - - - - - {{ 'Submit'|trans }} - - + + + + + + {{ 'Submit'|trans }} + + {% endif %}
\ No newline at end of file From 8df29ef60559a61e638886d9201b9ae642258933 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 6 Oct 2023 03:28:36 +0300 Subject: [PATCH 120/434] draft torrent submit form #11 --- config/services.yaml | 7 +- public/asset/default/css/common.css | 5 +- src/Controller/TorrentController.php | 181 ++++++++++++++++++ src/Entity/Torrent.php | 103 ++++++++++ src/Entity/TorrentLocales.php | 103 ++++++++++ src/Entity/TorrentSensitive.php | 103 ++++++++++ src/Repository/TorrentLocalesRepository.php | 23 +++ src/Repository/TorrentRepository.php | 23 +++ src/Repository/TorrentSensitiveRepository.php | 23 +++ src/Service/TorrentService.php | 163 ++++++++++++++++ templates/default/torrent/info.html.twig | 2 + templates/default/torrent/submit.html.twig | 80 ++++++++ 12 files changed, 810 insertions(+), 6 deletions(-) create mode 100644 src/Controller/TorrentController.php create mode 100644 src/Entity/Torrent.php create mode 100644 src/Entity/TorrentLocales.php create mode 100644 src/Entity/TorrentSensitive.php create mode 100644 src/Repository/TorrentLocalesRepository.php create mode 100644 src/Repository/TorrentRepository.php create mode 100644 src/Repository/TorrentSensitiveRepository.php create mode 100644 src/Service/TorrentService.php create mode 100644 templates/default/torrent/info.html.twig create mode 100644 templates/default/torrent/submit.html.twig diff --git a/config/services.yaml b/config/services.yaml index 1bccbb7..41f446e 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -12,10 +12,9 @@ parameters: app.page.title.length.max: '%env(APP_PAGE_TITLE_LENGTH_MAX)%' app.page.description.length.min: '%env(APP_PAGE_DESCRIPTION_LENGTH_MIN)%' app.page.description.length.max: '%env(APP_PAGE_DESCRIPTION_LENGTH_MAX)%' - app.page.torrent.quantity.min: '%env(APP_PAGE_TORRENT_QUANTITY_MIN)%' - app.page.torrent.quantity.max: '%env(APP_PAGE_TORRENT_QUANTITY_MAX)%' - app.page.torrent.size.min: '%env(APP_PAGE_TORRENT_SIZE_MIN)%' - app.page.torrent.size.max: '%env(APP_PAGE_TORRENT_SIZE_MAX)%' + app.page.torrent.file.quantity.min: '%env(APP_PAGE_TORRENT_FILE_QUANTITY_MIN)%' + app.page.torrent.file.quantity.max: '%env(APP_PAGE_TORRENT_FILE_QUANTITY_MAX)%' + app.torrent.size.max: '%env(APP_TORRENT_FILE_SIZE_MAX)%' services: # default configuration for services in *this* file diff --git a/public/asset/default/css/common.css b/public/asset/default/css/common.css index f7a537f..f285013 100644 --- a/public/asset/default/css/common.css +++ b/public/asset/default/css/common.css @@ -64,8 +64,9 @@ input:focus { color: #fff; } -textarea { - min-height: 160px; +textarea, +select[multiple="multiple"] { + min-height: 260px; } /* @TODO improve focus out diff --git a/src/Controller/TorrentController.php b/src/Controller/TorrentController.php new file mode 100644 index 0000000..7140055 --- /dev/null +++ b/src/Controller/TorrentController.php @@ -0,0 +1,181 @@ + '\d+' + ], + methods: + [ + 'GET' + ] + )] + public function info( + Request $request, + UserService $userService, + TorrentService $torrentService + ): Response + { + // Init user + $user = $userService->init( + $request->getClientIp() + ); + + return $this->render('default/torrent/info.html.twig', [ + 'title' => 'test' + ]); + } + + #[Route( + '/{_locale}/submit/torrent', + name: 'torrent_submit', + methods: + [ + 'GET', + 'POST' + ] + )] + public function submit( + Request $request, + TranslatorInterface $translator, + UserService $userService, + TorrentService $torrentService + ): Response + { + // Init user + $user = $userService->init( + $request->getClientIp() + ); + + if (!$user->isStatus()) + { + // @TODO + throw new \Exception( + $translator->trans('Access denied') + ); + } + + // Init form + $form = + [ + 'locales' => + [ + 'error' => [], + 'attribute' => + [ + 'value' => $request->get('locales') ? $request->get('locales') : [$request->get('_locale')], + 'placeholder' => $translator->trans('Content language') + ] + ], + 'torrent' => + [ + 'error' => [], + 'attribute' => + [ + 'value' => null, // is local file, there is no values passed + 'placeholder' => $translator->trans('Select torrent file') + ] + ], + 'sensitive' => + [ + 'error' => [], + 'attribute' => + [ + 'value' => $request->get('sensitive'), + 'placeholder' => $translator->trans('Apply sensitive filters for this publication'), + ] + ] + ]; + + // Process request + if ($request->isMethod('post')) + { + /// Locales + $locales = []; + if ($request->get('locales')) + { + foreach ((array) $request->get('locales') as $locale) + { + if (in_array($locale, explode('|', $this->getParameter('app.locales')))) + { + $locales[] = $locale; + } + } + } + + //// At least one valid locale required + if (!$locales) + { + $form['locales']['error'][] = $translator->trans('At least one locale required'); + } + + /// Torrent + if ($file = $request->files->get('torrent')) + { + //// Validate torrent file + if (filesize($file->getPathName()) > $this->getParameter('app.torrent.size.max')) + { + $form['torrent']['error'][] = $translator->trans('Torrent file out of size limit'); + } + + if (empty($torrentService->getTorrentFilenameByFilepath($file->getPathName()))) + { + $form['torrent']['error'][] = $translator->trans('Could not parse torrent file'); + } + } + + else + { + $form['torrent']['error'][] = $translator->trans('Torrent file required'); + } + + // Request is valid + if (empty($form['torrent']['error']) && empty($form['locales']['error'])) + { + // Save data + $torrent = $torrentService->submit( + $file->getPathName(), + $user->getId(), + time(), + (array) $locales, + (bool) $request->get('sensitive'), + $user->isApproved() + ); + + // Redirect to info page created + return $this->redirectToRoute( + 'torrent_info', + [ + '_locale' => $request->get('_locale'), + 'id' => $torrent->getId() + ] + ); + } + } + + // Render form template + return $this->render( + 'default/torrent/submit.html.twig', + [ + 'locales' => explode('|', $this->getParameter('app.locales')), + 'form' => $form, + ] + ); + } +} diff --git a/src/Entity/Torrent.php b/src/Entity/Torrent.php new file mode 100644 index 0000000..20aba38 --- /dev/null +++ b/src/Entity/Torrent.php @@ -0,0 +1,103 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getFilename(): ?string + { + return $this->filename; + } + + public function setFilename(string $filename): static + { + $this->filename = $filename; + + return $this; + } + + public function getKeywords(): ?string + { + return $this->keywords; + } + + public function setKeywords(?string $keywords): static + { + $this->keywords = $keywords; + + return $this; + } + + public function getSeeders(): ?int + { + return $this->seeders; + } + + public function setSeeders(?int $seeders): static + { + $this->seeders = $seeders; + + return $this; + } + + public function getPeers(): ?int + { + return $this->peers; + } + + public function setPeers(?int $peers): static + { + $this->peers = $peers; + + return $this; + } + + public function getLeechers(): ?int + { + return $this->leechers; + } + + public function setLeechers(?int $leechers): static + { + $this->leechers = $leechers; + + return $this; + } +} diff --git a/src/Entity/TorrentLocales.php b/src/Entity/TorrentLocales.php new file mode 100644 index 0000000..5e6c10f --- /dev/null +++ b/src/Entity/TorrentLocales.php @@ -0,0 +1,103 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getTorrentId(): ?int + { + return $this->torrentId; + } + + public function setTorrentId(int $torrentId): static + { + $this->torrentId = $torrentId; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function getValue(): array + { + return $this->value; + } + + public function setValue(array $value): static + { + $this->value = $value; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } +} diff --git a/src/Entity/TorrentSensitive.php b/src/Entity/TorrentSensitive.php new file mode 100644 index 0000000..9c1560f --- /dev/null +++ b/src/Entity/TorrentSensitive.php @@ -0,0 +1,103 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getTorrentId(): ?int + { + return $this->torrentId; + } + + public function setTorrentId(int $torrentId): static + { + $this->torrentId = $torrentId; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function isValue(): ?bool + { + return $this->value; + } + + public function setValue(bool $value): static + { + $this->value = $value; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } +} diff --git a/src/Repository/TorrentLocalesRepository.php b/src/Repository/TorrentLocalesRepository.php new file mode 100644 index 0000000..8ba7b73 --- /dev/null +++ b/src/Repository/TorrentLocalesRepository.php @@ -0,0 +1,23 @@ + + * + * @method TorrentLocales|null find($id, $lockMode = null, $lockVersion = null) + * @method TorrentLocales|null findOneBy(array $criteria, array $orderBy = null) + * @method TorrentLocales[] findAll() + * @method TorrentLocales[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class TorrentLocalesRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, TorrentLocales::class); + } +} diff --git a/src/Repository/TorrentRepository.php b/src/Repository/TorrentRepository.php new file mode 100644 index 0000000..53b6358 --- /dev/null +++ b/src/Repository/TorrentRepository.php @@ -0,0 +1,23 @@ + + * + * @method Torrent|null find($id, $lockMode = null, $lockVersion = null) + * @method Torrent|null findOneBy(array $criteria, array $orderBy = null) + * @method Torrent[] findAll() + * @method Torrent[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class TorrentRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Torrent::class); + } +} diff --git a/src/Repository/TorrentSensitiveRepository.php b/src/Repository/TorrentSensitiveRepository.php new file mode 100644 index 0000000..e21c7e6 --- /dev/null +++ b/src/Repository/TorrentSensitiveRepository.php @@ -0,0 +1,23 @@ + + * + * @method TorrentSensitive|null find($id, $lockMode = null, $lockVersion = null) + * @method TorrentSensitive|null findOneBy(array $criteria, array $orderBy = null) + * @method TorrentSensitive[] findAll() + * @method TorrentSensitive[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class TorrentSensitiveRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, TorrentSensitive::class); + } +} diff --git a/src/Service/TorrentService.php b/src/Service/TorrentService.php new file mode 100644 index 0000000..df14e9f --- /dev/null +++ b/src/Service/TorrentService.php @@ -0,0 +1,163 @@ +entityManager = $entityManager; + } + + public function decodeTorrentByFilepath(string $filepath): array + { + $decoder = new \BitTorrent\Decoder(); + + return $decoder->decodeFile($filepath); + } + + public function getTorrentFilenameByFilepath(string $filepath): string + { + $data = $this->decodeTorrentByFilepath($filepath); + + if (!empty($data['info']['name'])) + { + return $data['info']['name']; + } + + return $data['info']['name']; + } + + public function getTorrentKeywordsByFilepath(string $filepath): string + { + $data = $this->decodeTorrentByFilepath($filepath); + + if (!empty($data['info']['name'])) + { + return mb_strtolower( + preg_replace( + '/[\s]+/', + ' ', + preg_replace( + '/[\W]+/', + ' ', + $data['info']['name'] + ) + ) + ); + } + + return ''; + } + + public function submit( + string $filepath, + int $userId, + int $added, + array $locales, + bool $sensitive, + bool $approved + ): ?Torrent + { + $torrent = $this->saveTorrent( + $this->getTorrentFilenameByFilepath($filepath), + $this->getTorrentKeywordsByFilepath($filepath) + ); + + if (!empty($locales)) + { + $this->saveTorrentLocales( + $torrent->getId(), + $userId, + $added, + $locales, + $approved + ); + } + + $this->saveTorrentSensitive( + $torrent->getId(), + $userId, + $added, + $sensitive, + $approved + ); + + return $torrent; + } + + public function saveTorrent( + string $filepath, + string $keywords + ): ?Torrent + { + $torrent = new Torrent(); + + $torrent->setFilename($filepath); + $torrent->setKeywords($keywords); + + $this->entityManager->persist($torrent); + $this->entityManager->flush(); + + return $torrent; + } + + public function saveTorrentLocales( + int $torrentId, + int $userId, + int $added, + array $value, + bool $approved + ): ?TorrentLocales + { + $torrentLocales = new TorrentLocales(); + + $torrentLocales->setTorrentId($torrentId); + $torrentLocales->setUserId($userId); + $torrentLocales->setAdded($added); + $torrentLocales->setValue($value); + $torrentLocales->setApproved($approved); + + $this->entityManager->persist($torrentLocales); + $this->entityManager->flush(); + + return $torrentLocales; + } + + public function saveTorrentSensitive( + int $torrentId, + int $userId, + int $added, + bool $value, + bool $approved + ): ?TorrentSensitive + { + $torrentSensitive = new TorrentSensitive(); + + $torrentSensitive->setTorrentId($torrentId); + $torrentSensitive->setUserId($userId); + $torrentSensitive->setAdded($added); + $torrentSensitive->setValue($value); + $torrentSensitive->setApproved($approved); + + $this->entityManager->persist($torrentSensitive); + $this->entityManager->flush(); + + return $torrentSensitive; + } +} \ No newline at end of file diff --git a/templates/default/torrent/info.html.twig b/templates/default/torrent/info.html.twig new file mode 100644 index 0000000..3389f18 --- /dev/null +++ b/templates/default/torrent/info.html.twig @@ -0,0 +1,2 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ title }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/torrent/submit.html.twig b/templates/default/torrent/submit.html.twig new file mode 100644 index 0000000..1b9444e --- /dev/null +++ b/templates/default/torrent/submit.html.twig @@ -0,0 +1,80 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{'Submit torrent'|trans }} - {{ name }}{% endblock %} +{% block main_content %} +
+
+

{{'Submit torrent'|trans }}

+
+
+
+ + + + + + + {% for error in form.torrent.error %} +
+ {{ error }} +
+ {% endfor %} + +
+
+ + + + + + + {% for error in form.locales.error %} +
+ {{ error }} +
+ {% endfor %} +
+ +
+
+
+ + + + + + + +
+
+ +
+
+
+{% endblock %} From c0cc029350fd5d3f00d62af493a4077a3197abeb Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 6 Oct 2023 04:12:14 +0300 Subject: [PATCH 121/434] update page form dependencies #19 --- .env | 6 +- src/Controller/PageController.php | 144 ++++++++++----- src/Controller/UserController.php | 24 ++- src/Entity/Activity.php | 99 ++++++++++ src/Entity/PageDescription.php | 118 ++++++++++++ src/Entity/PageSensitive.php | 118 ++++++++++++ src/Entity/PageTitle.php | 118 ++++++++++++ src/Entity/PageTorrents.php | 103 +++++++++++ src/Repository/ActivityRepository.php | 47 +++++ src/Repository/PageDescriptionRepository.php | 23 +++ src/Repository/PageRepository.php | 25 --- src/Repository/PageSensitiveRepository.php | 23 +++ src/Repository/PageTitleRepository.php | 23 +++ src/Repository/PageTorrentsRepository.php | 23 +++ src/Repository/UserRepository.php | 9 - src/Service/ActivityService.php | 54 ++++++ src/Service/PageService.php | 179 +++++++++++++++++-- src/Service/UserService.php | 5 - templates/default/page/info.html.twig | 2 + templates/default/page/submit.html.twig | 4 +- templates/default/torrent/submit.html.twig | 2 +- templates/default/user/module.html.twig | 2 +- 22 files changed, 1049 insertions(+), 102 deletions(-) create mode 100644 src/Entity/Activity.php create mode 100644 src/Entity/PageDescription.php create mode 100644 src/Entity/PageSensitive.php create mode 100644 src/Entity/PageTitle.php create mode 100644 src/Entity/PageTorrents.php create mode 100644 src/Repository/ActivityRepository.php create mode 100644 src/Repository/PageDescriptionRepository.php create mode 100644 src/Repository/PageSensitiveRepository.php create mode 100644 src/Repository/PageTitleRepository.php create mode 100644 src/Repository/PageTorrentsRepository.php create mode 100644 src/Service/ActivityService.php create mode 100644 templates/default/page/info.html.twig diff --git a/.env b/.env index 284b058..ecdff9a 100644 --- a/.env +++ b/.env @@ -56,6 +56,6 @@ APP_PAGE_TITLE_LENGTH_MIN=10 APP_PAGE_TITLE_LENGTH_MAX=255 APP_PAGE_DESCRIPTION_LENGTH_MIN=0 APP_PAGE_DESCRIPTION_LENGTH_MAX=10000 -APP_PAGE_TORRENT_QUANTITY_MIN=1 -APP_PAGE_TORRENT_QUANTITY_MAX=100 -APP_PAGE_TORRENT_SIZE_MAX=1024000 \ No newline at end of file +APP_PAGE_TORRENT_FILE_QUANTITY_MIN=1 +APP_PAGE_TORRENT_FILE_QUANTITY_MAX=100 +APP_TORRENT_FILE_SIZE_MAX=1024000 \ No newline at end of file diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index 1a580b2..60a3cf6 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -11,19 +11,54 @@ use Symfony\Component\HttpFoundation\Request; use App\Service\UserService; use App\Service\PageService; +use App\Service\TorrentService; use App\Service\TimeService; class PageController extends AbstractController { #[Route( - '/{_locale}/page/submit', - name: 'page_submit' + '/{_locale}/page/{id}', + name: 'page_info', + requirements: + [ + 'id' => '\d+' + ], + methods: + [ + 'GET' + ] + )] + public function info( + Request $request, + TranslatorInterface $translator, + UserService $userService + ): Response + { + // Init user + $user = $userService->init( + $request->getClientIp() + ); + + return $this->render('default/page/info.html.twig', [ + 'title' => 'test' + ]); + } + + #[Route( + '/{_locale}/submit/page', + name: 'page_submit', + methods: + [ + 'GET', + 'POST' + ] )] public function submit( Request $request, TranslatorInterface $translator, UserService $userService, PageService $pageService, + PageService $torrentService ): Response { // Init user @@ -45,8 +80,11 @@ class PageController extends AbstractController 'locale' => [ 'error' => [], - 'value' => $request->get('_locale'), - 'placeholder' => $translator->trans('Content language'), + 'attribute' => + [ + 'value' => $request->get('_locale'), + 'placeholder' => $translator->trans('Content language') + ] ], 'title' => [ @@ -57,7 +95,7 @@ class PageController extends AbstractController 'minlength' => $this->getParameter('app.page.title.length.min'), 'maxlength' => $this->getParameter('app.page.title.length.max'), 'placeholder' => sprintf( - $translator->trans('Page title text (%s-%s chars)'), + $translator->trans('Page title (%s-%s chars)'), number_format($this->getParameter('app.page.title.length.min')), number_format($this->getParameter('app.page.title.length.max')) ), @@ -72,7 +110,7 @@ class PageController extends AbstractController 'minlength' => $this->getParameter('app.page.description.length.min'), 'maxlength' => $this->getParameter('app.page.description.length.max'), 'placeholder' => sprintf( - $translator->trans('Page description text (%s-%s chars)'), + $translator->trans('Page description (%s-%s chars)'), number_format($this->getParameter('app.page.description.length.min')), number_format($this->getParameter('app.page.description.length.max')) ), @@ -83,7 +121,11 @@ class PageController extends AbstractController 'error' => [], 'attribute' => [ - 'placeholder' => $translator->trans('Select torrent files'), + 'placeholder' => sprintf( + $translator->trans('Append %s-%s torrent files'), + $this->getParameter('app.page.torrent.file.quantity.min'), + $this->getParameter('app.page.torrent.file.quantity.max') + ) ] ], 'sensitive' => @@ -100,20 +142,12 @@ class PageController extends AbstractController // Process request if ($request->isMethod('post')) { - // Init new - $page = $pageService->new(); - /// Locale if (!in_array($request->get('locale'), explode('|', $this->getParameter('app.locales')))) { $form['locale']['error'][] = $translator->trans('Requested locale not supported'); } - else - { - // $request->get('locale') - } - /// Title if (mb_strlen($request->get('title')) < $this->getParameter('app.page.title.length.min') || mb_strlen($request->get('title')) > $this->getParameter('app.page.title.length.max')) @@ -125,11 +159,6 @@ class PageController extends AbstractController ); } - else - { - // $request->get('title') - } - /// Description if (mb_strlen($request->get('description')) < $this->getParameter('app.page.description.length.min') || mb_strlen($request->get('description')) > $this->getParameter('app.page.description.length.max')) @@ -141,13 +170,9 @@ class PageController extends AbstractController ); } - else - { - // $request->get('description') - } - /// Torrents $total = 0; + $torrents = []; if ($files = $request->files->get('torrents')) { @@ -157,42 +182,79 @@ class PageController extends AbstractController $total++; //// File size - if (filesize($file->getPathName()) > $this->getParameter('app.page.torrent.size.max')) + if (filesize($file->getPathName()) > $this->getParameter('app.torrent.size.max')) { $form['torrents']['error'][] = $translator->trans('Torrent file out of size limit'); + + continue; + } + + if (empty($torrentService->getTorrentFilenameByFilepath($file->getPathName()))) + { + $form['torrent']['error'][] = $translator->trans('Could not parse torrent file'); + + continue; } //// Content - $decoder = new \BitTorrent\Decoder(); - $decodedFile = $decoder->decodeFile( - $file->getPathName() + $torrent = $torrentService->submit( + $file->getPathName(), + $user->getId(), + time(), + (array) $locales, + (bool) $request->get('sensitive'), + $user->isApproved() ); - // var_dump($decodedFile['info']['name']); + $torrents[] = $torrent->getId(); } } - if ($total < $this->getParameter('app.page.torrent.quantity.min') || - $total > $this->getParameter('app.page.torrent.quantity.max')) + if ($total < $this->getParameter('app.page.torrent.file.quantity.min') || + $total > $this->getParameter('app.page.torrent.file.quantity.max')) { $form['torrents']['error'][] = sprintf( $translator->trans('Torrents quantity out of %s-%s range'), - number_format($this->getParameter('app.page.torrent.quantity.min')), - number_format($this->getParameter('app.page.torrent.quantity.max')) + number_format($this->getParameter('app.page.torrent.file.quantity.min')), + number_format($this->getParameter('app.page.torrent.file.quantity.max')) ); } - if (empty($error)) + if (empty($form['locale']['error']) && + empty($form['title']['error']) && + empty($form['description']['error']) && + empty($form['torrents']['error']) + ) { - // isset($request->get('sensitive')) - // $pageService->save($page); + $page = $pageService->submit( + $user->getId(), + time(), + (string) $request->get('locale'), + (string) $request->get('title'), + (string) $request->get('description'), + (array) $torrents, + (bool) $request->get('sensitive'), + $user->isApproved() + ); + + // Redirect + return $this->redirectToRoute( + 'page_info', + [ + '_locale' => $request->get('_locale'), + 'id' => $page->getId() + ] + ); } } - return $this->render('default/page/submit.html.twig', [ - 'locales' => explode('|', $this->getParameter('app.locales')), - 'form' => $form, - ]); + return $this->render( + 'default/page/submit.html.twig', + [ + 'locales' => explode('|', $this->getParameter('app.locales')), + 'form' => $form, + ] + ); } } \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 6c867f4..271b723 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -7,6 +7,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; +use App\Service\ActivityService; use App\Service\UserService; use App\Service\TimeService; @@ -36,35 +37,48 @@ class UserController extends AbstractController )] public function index( Request $request, + ActivityService $activityService, UserService $userService, TimeService $timeService ): Response { // Init user session - $userService->init( + $user = $userService->init( $request->getClientIp() ); // Build activity history $activities = []; - foreach ($userService->getAllByAddedFieldDesc() as $user) + + /* + foreach ($activityService->findLast($user->isModerator()) as $activity) { + if (!$activity->getUserId()) + { + continue; + } + + $activityUser = $userService->get( + $activity->getUserId() + ); + $activities[] = [ 'user' => [ - 'id' => $user->getId(), + 'id' => $activityUser->getId(), 'identicon' => $userService->identicon( - $user->getAddress(), + $activityUser->getAddress(), 24 ) ], 'type' => 'join', 'added' => $timeService->ago( - $user->getAdded() + $activity->getAdded() ) ]; } + */ return $this->render( 'default/user/dashboard.html.twig', diff --git a/src/Entity/Activity.php b/src/Entity/Activity.php new file mode 100644 index 0000000..3d27fef --- /dev/null +++ b/src/Entity/Activity.php @@ -0,0 +1,99 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getEvent(): ?string + { + return $this->event; + } + + public function setEvent(string $event): static + { + $this->event = $event; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(?int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getPageId(): ?int + { + return $this->pageId; + } + + public function setPageId(?int $pageId): static + { + $this->pageId = $pageId; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } +} diff --git a/src/Entity/PageDescription.php b/src/Entity/PageDescription.php new file mode 100644 index 0000000..ae85a02 --- /dev/null +++ b/src/Entity/PageDescription.php @@ -0,0 +1,118 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getPageId(): ?int + { + return $this->pageId; + } + + public function setPageId(int $pageId): static + { + $this->pageId = $pageId; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(string $locale): static + { + $this->locale = $locale; + + return $this; + } + + public function getValue(): ?string + { + return $this->value; + } + + public function setValue(string $value): static + { + $this->value = $value; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } +} diff --git a/src/Entity/PageSensitive.php b/src/Entity/PageSensitive.php new file mode 100644 index 0000000..7af1e78 --- /dev/null +++ b/src/Entity/PageSensitive.php @@ -0,0 +1,118 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getPageId(): ?int + { + return $this->pageId; + } + + public function setPageId(int $pageId): static + { + $this->pageId = $pageId; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(string $locale): static + { + $this->locale = $locale; + + return $this; + } + + public function isValue(): ?bool + { + return $this->value; + } + + public function setValue(bool $value): static + { + $this->value = $value; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } +} diff --git a/src/Entity/PageTitle.php b/src/Entity/PageTitle.php new file mode 100644 index 0000000..1dbce61 --- /dev/null +++ b/src/Entity/PageTitle.php @@ -0,0 +1,118 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getPageId(): ?int + { + return $this->pageId; + } + + public function setPageId(int $pageId): static + { + $this->pageId = $pageId; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(string $locale): static + { + $this->locale = $locale; + + return $this; + } + + public function getValue(): ?string + { + return $this->value; + } + + public function setValue(string $value): static + { + $this->value = $value; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } +} diff --git a/src/Entity/PageTorrents.php b/src/Entity/PageTorrents.php new file mode 100644 index 0000000..81a3811 --- /dev/null +++ b/src/Entity/PageTorrents.php @@ -0,0 +1,103 @@ +id; + } + + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + public function getPageId(): ?int + { + return $this->pageId; + } + + public function setPageId(int $pageId): static + { + $this->pageId = $pageId; + + return $this; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUserId(int $userId): static + { + $this->userId = $userId; + + return $this; + } + + public function getTorrentsId(): array + { + return $this->torrentsId; + } + + public function setTorrentsId(array $torrentsId): static + { + $this->torrentsId = $torrentsId; + + return $this; + } + + public function getAdded(): ?int + { + return $this->added; + } + + public function setAdded(int $added): static + { + $this->added = $added; + + return $this; + } + + public function isApproved(): ?bool + { + return $this->approved; + } + + public function setApproved(bool $approved): static + { + $this->approved = $approved; + + return $this; + } +} diff --git a/src/Repository/ActivityRepository.php b/src/Repository/ActivityRepository.php new file mode 100644 index 0000000..6d26c74 --- /dev/null +++ b/src/Repository/ActivityRepository.php @@ -0,0 +1,47 @@ + + * + * @method Activity|null find($id, $lockMode = null, $lockVersion = null) + * @method Activity|null findOneBy(array $criteria, array $orderBy = null) + * @method Activity[] findAll() + * @method Activity[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ActivityRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Activity::class); + } + + public function findLast(int $start = 0, int $limit = 10): array + { + return $this->createQueryBuilder('a') + ->orderBy('a.id', 'DESC') // same to a.added + ->setFirstResult($start) + ->setMaxResults($limit) + ->getQuery() + ->getResult() + ; + } + + public function findLastByApprovedField(bool $approved, int $start = 0, int $limit = 10): array + { + return $this->createQueryBuilder('a') + ->orderBy('a.id', 'DESC') // same to a.added + ->where('a.approved = :approved') + ->setParameter('approved', $approved) + ->setFirstResult($start) + ->setMaxResults($limit) + ->getQuery() + ->getResult() + ; + } +} diff --git a/src/Repository/PageDescriptionRepository.php b/src/Repository/PageDescriptionRepository.php new file mode 100644 index 0000000..06d9036 --- /dev/null +++ b/src/Repository/PageDescriptionRepository.php @@ -0,0 +1,23 @@ + + * + * @method PageDescription|null find($id, $lockMode = null, $lockVersion = null) + * @method PageDescription|null findOneBy(array $criteria, array $orderBy = null) + * @method PageDescription[] findAll() + * @method PageDescription[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PageDescriptionRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, PageDescription::class); + } +} diff --git a/src/Repository/PageRepository.php b/src/Repository/PageRepository.php index 985f0d6..f0baa09 100644 --- a/src/Repository/PageRepository.php +++ b/src/Repository/PageRepository.php @@ -20,29 +20,4 @@ class PageRepository extends ServiceEntityRepository { parent::__construct($registry, Page::class); } - -// /** -// * @return Page[] Returns an array of Page objects -// */ -// public function findByExampleField($value): array -// { -// return $this->createQueryBuilder('p') -// ->andWhere('p.exampleField = :val') -// ->setParameter('val', $value) -// ->orderBy('p.id', 'ASC') -// ->setMaxResults(10) -// ->getQuery() -// ->getResult() -// ; -// } - -// public function findOneBySomeField($value): ?Page -// { -// return $this->createQueryBuilder('p') -// ->andWhere('p.exampleField = :val') -// ->setParameter('val', $value) -// ->getQuery() -// ->getOneOrNullResult() -// ; -// } } diff --git a/src/Repository/PageSensitiveRepository.php b/src/Repository/PageSensitiveRepository.php new file mode 100644 index 0000000..9f8b321 --- /dev/null +++ b/src/Repository/PageSensitiveRepository.php @@ -0,0 +1,23 @@ + + * + * @method PageSensitive|null find($id, $lockMode = null, $lockVersion = null) + * @method PageSensitive|null findOneBy(array $criteria, array $orderBy = null) + * @method PageSensitive[] findAll() + * @method PageSensitive[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PageSensitiveRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, PageSensitive::class); + } +} diff --git a/src/Repository/PageTitleRepository.php b/src/Repository/PageTitleRepository.php new file mode 100644 index 0000000..346ccd2 --- /dev/null +++ b/src/Repository/PageTitleRepository.php @@ -0,0 +1,23 @@ + + * + * @method PageTitle|null find($id, $lockMode = null, $lockVersion = null) + * @method PageTitle|null findOneBy(array $criteria, array $orderBy = null) + * @method PageTitle[] findAll() + * @method PageTitle[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PageTitleRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, PageTitle::class); + } +} diff --git a/src/Repository/PageTorrentsRepository.php b/src/Repository/PageTorrentsRepository.php new file mode 100644 index 0000000..01c6627 --- /dev/null +++ b/src/Repository/PageTorrentsRepository.php @@ -0,0 +1,23 @@ + + * + * @method PageTorrents|null find($id, $lockMode = null, $lockVersion = null) + * @method PageTorrents|null findOneBy(array $criteria, array $orderBy = null) + * @method PageTorrents[] findAll() + * @method PageTorrents[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PageTorrentsRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, PageTorrents::class); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 202d56d..49896f3 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -40,13 +40,4 @@ class UserRepository extends ServiceEntityRepository ->getOneOrNullResult() ; } - - public function findAllByAddedFieldDesc(): array - { - return $this->createQueryBuilder('u') - ->orderBy('u.added', 'DESC') - ->getQuery() - ->getResult() - ; - } } diff --git a/src/Service/ActivityService.php b/src/Service/ActivityService.php new file mode 100644 index 0000000..268fb0b --- /dev/null +++ b/src/Service/ActivityService.php @@ -0,0 +1,54 @@ +entityManager = $entityManager; + $this->activityRepository = $entityManager->getRepository(Activity::class); + $this->parameterBagInterface = $parameterBagInterface; + } + + public function addEvent(int $userId, string $event, array $data): ?Activity + { + $activity = new Activity(); + + $activity->setEvent($event); + $activity->setUserId($userId); + $activity->setApproved($approved); + $activity->setAdded(time()); + + $this->entityManager->persist($activity); + $this->entityManager->flush(); + + return $activity; + } + + public function findLast(bool $moderator): ?array + { + if ($moderator) + { + return $this->activityRepository->findLast(); + } + + else + { + return $this->activityRepository->findLastByApprovedField(true); + } + } +} \ No newline at end of file diff --git a/src/Service/PageService.php b/src/Service/PageService.php index ae50c44..97da0a5 100644 --- a/src/Service/PageService.php +++ b/src/Service/PageService.php @@ -3,35 +3,194 @@ namespace App\Service; use App\Entity\Page; -use App\Repository\PageRepository; -use Doctrine\ORM\EntityManagerInterface; +use App\Entity\PageTitle; +use App\Entity\PageDescription; +use App\Entity\PageTorrents; +use App\Entity\PageSensitive; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use App\Repository\PageRepository; +use App\Repository\PageTitleRepository; +use App\Repository\PageDescriptionRepository; +use App\Repository\PageSensitiveRepository; +use App\Repository\PageTorrentsRepository; + +use Doctrine\ORM\EntityManagerInterface; class PageService { private EntityManagerInterface $entityManager; - private PageRepository $pageRepository; private ParameterBagInterface $parameterBagInterface; public function __construct( EntityManagerInterface $entityManager, - ParameterBagInterface $parameterBagInterface ) { $this->entityManager = $entityManager; - $this->pageRepository = $entityManager->getRepository(Page::class); - $this->parameterBagInterface = $parameterBagInterface; } - public function new(): ?Page + public function submit( + int $added, + int $userId, + string $locale, + string $title, + string $description, + array $torrents, + bool $sensitive, + bool $approved + ): ?Page { - return new Page(); + $page = $this->addPage(); + + if (!empty($title)) + { + $pageTitle = $this->addPageTitle( + $page->getId(), + $userId, + $added, + $locale, + $title, + $approved + ); + } + + if (!empty($description)) + { + $pageDescription = $this->addPageDescription( + $page->getId(), + $userId, + $added, + $locale, + $description, + $approved + ); + } + + if (!empty($torrents)) + { + $pageTorrents = $this->addPageTorrents( + $page->getId(), + $userId, + $added, + $locale, + $torrents, + $approved + ); + } + + // @TODO + $pageSensitive = $this->addPageSensitive( + $page->getId(), + $userId, + $added, + $locale, + $description, + $approved + ); + + return $page; } - public function save(Page $page) : void + public function addPage(): ?Page { + $page = new Page(); + $this->entityManager->persist($page); $this->entityManager->flush(); + + return $page; + } + + public function addPageTitle( + int $pageId, + int $userId, + int $added, + string $locale, + string $value, + bool $approved + ): ?PageTitle + { + $pageTitle = new PageTitle(); + + $pageTitle->setPageId($pageId); + $pageTitle->setUserId($userId); + $pageTitle->setLocale($locale); + $pageTitle->setValue($value); + $pageTitle->setAdded($added); + $pageTitle->setApproved($approved); + + $this->entityManager->persist($pageTitle); + $this->entityManager->flush(); + + return $pageTitle; + } + + public function addPageDescription( + int $pageId, + int $userId, + int $added, + string $locale, + string $value, + bool $approved + ): ?PageDescription + { + $pageDescription = new PageDescription(); + + $pageDescription->setPageId($pageId); + $pageDescription->setUserId($userId); + $pageDescription->setAdded($added); + $pageDescription->setLocale($locale); + $pageDescription->setValue($value); + $pageDescription->setApproved($approved); + + $this->entityManager->persist($pageDescription); + $this->entityManager->flush(); + + return $pageDescription; + } + + public function addPageTorrents( + int $pageId, + int $userId, + int $added, + array $torrentsId, + bool $approved + ): ?PageTorrents + { + $pageTorrents = new PageTorrents(); + + $pageTorrents->setPageId($pageId); + $pageTorrents->setUserId($userId); + $pageTorrents->setAdded($added); + $pageTorrents->setTorrentsId($torrentsId); + $pageTorrents->setApproved($approved); + + $this->entityManager->persist($pageTorrents); + $this->entityManager->flush(); + + return $pageTorrents; + } + + public function addPageSensitive( + int $pageId, + int $userId, + int $added, + string $locale, + string $value, + bool $approved + ): ?PageSensitive + { + $pageSensitive = new PageSensitive(); + + $pageSensitive->setPageId($pageId); + $pageSensitive->setUserId($userId); + $pageSensitive->setAdded($added); + $pageSensitive->setLocale($locale); + $pageSensitive->setValue($value); + $pageSensitive->setApproved($approved); + + $this->entityManager->persist($pageSensitive); + $this->entityManager->flush(); + + return $pageSensitive; } } \ No newline at end of file diff --git a/src/Service/UserService.php b/src/Service/UserService.php index 033c459..c4ade42 100644 --- a/src/Service/UserService.php +++ b/src/Service/UserService.php @@ -58,11 +58,6 @@ class UserService return $this->userRepository->findOneByIdField($id); } - public function getAllByAddedFieldDesc(): array - { - return $this->userRepository->findAllByAddedFieldDesc(); - } - public function identicon( mixed $value, int $size = 16, diff --git a/templates/default/page/info.html.twig b/templates/default/page/info.html.twig new file mode 100644 index 0000000..3389f18 --- /dev/null +++ b/templates/default/page/info.html.twig @@ -0,0 +1,2 @@ +{% extends 'default/layout.html.twig' %} +{% block title %}{{ title }} - {{ name }}{% endblock %} \ No newline at end of file diff --git a/templates/default/page/submit.html.twig b/templates/default/page/submit.html.twig index 19d4cb6..2f1eea4 100644 --- a/templates/default/page/submit.html.twig +++ b/templates/default/page/submit.html.twig @@ -10,14 +10,14 @@ - + + {% for locale in locales %} + {% if locale in form.locales.attribute.value %} + + {% else %} + + {% endif %} + {% endfor %} + {# + + #} + +
+
+ + +
+ {% for edition in editions %} +
+ {% if edition.active %} + {{ edition.added | format_ago }} + {% else %} + + {{ edition.added | format_ago }} + + {% endif %} + {{ 'by'|trans }} + + {{'identicon'|trans }} + +
+ {% if edition.approved %} + + + + + + {% else %} + + + + + + {% endif %} +
+
+ {% endfor %} +{% endblock %} diff --git a/templates/default/torrent/info.html.twig b/templates/default/torrent/info.html.twig index 2a131a9..d249e63 100644 --- a/templates/default/torrent/info.html.twig +++ b/templates/default/torrent/info.html.twig @@ -20,11 +20,13 @@ {% endmacro %} {% from _self import recursive_file_tree %} {% extends 'default/layout.html.twig' %} -{% block title %}{{ 'Torrent'|trans }} #{{ torrent.id }} - {{ name }}{% endblock %} +{% block title %}{% if file.name %}{{ file.name }} - {% endif %}{{ 'Torrent'|trans }} #{{ torrent.id }} - {{ name }}{% endblock %} {% block main_content %}
-

{{ 'Torrent'|trans }} #{{ torrent.id }}

+

+ {{ 'Torrent'|trans }} #{{ torrent.id }} +

@@ -43,198 +45,178 @@ #}
- - - {% if file.name %} - - - - - - - {% endif %} - {% if file.created %} - - - - - - - {% endif %} - {% if file.hash.v1 %} - - - - - - - {% endif %} - {% if file.hash.v2 %} - - - - - - - {% endif %} - {% if file['created by'] is defined %} - - - - - - - {% endif %} - {% if file.comment is defined %} - - - - - - - {% endif %} - - - - {% for tree in file.tree %} - - - - {% endfor %} - - - - - - - {% for tracker in trackers %} - - - - {% endfor %} - {# - - - - - #} - {% for announces in trackers %} - {% for tracker in announces %} - - - - - {% endfor %} + {% endfor %} - - - - - - - - - + {% endfor %} + +
+ + + + + + {{ 'Locales'|trans }} +
+
{% if torrent.locales %} -
- - - {% else %} - - - +
+ {% for i, locale in torrent.locales.value %}{% if i > 0 %},{% endif %} {{ locale|locale_name(locale)|u.title }}{% endfor %} +
{% endif %} - - - - - - - - - - {% if torrent.pages %} - - - - {% else %} - - - - {% endif %} - -
- {{ 'Name'|trans }} -
- {{ file.name }} -
- {{ 'Created'|trans }} -
- {{ file.created | format_date }} -
- {{ 'Info hash v1'|trans }} -
- {{ file.hash.v1 }} -
- {{ 'Info hash v2'|trans }} -
- {{ file.hash.v2 }} -
- {{ 'Generated'|trans }} -
- {{ file['created by'] }} -
- {{ 'Comment'|trans }} -
- {{ file.comment }} -
- {{ 'Files'|trans }} -
- {{ recursive_file_tree(tree) }} -
- {{ 'Trackers'|trans }} -
 
+
+ {{ 'Common'|trans }} +
+
+ + + {% if file.name %} + + + + + {% endif %} + {% if file.created %} + + + + + {% endif %} + + {% if file.size %} + + + + + {% endif %} + {% if file.pieces %} + + + + + {% endif %} + {% if file.hash.v1 %} + + + + + {% endif %} + {% if file.hash.v2 %} + + + + + {% endif %} + {% if file.source %} + + + + + {% endif %} + {% if file.software %} + + + + + {% endif %} + {% if file.comment %} + + + + + {% endif %} + +
+ {{ 'Filename'|trans }} + + {{ file.name }} +
+ {{ 'Created'|trans }} + + {{ file.created | format_date }} +
+ {{ 'Size'|trans }} + + {{ file.size | format_bytes }} +
+ {{ 'Pieces'|trans }} + + {{ file.pieces | format_number }} +
+ {{ 'Info hash v1'|trans }} + + {{ file.hash.v1 }} +
+ {{ 'Info hash v2'|trans }} + + {{ file.hash.v2 }} +
+ {{ 'Source'|trans }} + + {{ file.source }} +
+ {{ 'Software'|trans }} + + {{ file.software }} +
+ {{ 'Comment'|trans }} + + {{ file.comment }} +
+
+
+ {{ 'Files'|trans }} +
+
+ {% for tree in file.tree %} + {{ recursive_file_tree(tree) }} + {% endfor %} +
+
+ {{ 'Trackers'|trans }} +
+
+ {% for tracker in trackers %} +
+ {{ tracker }} +
+ {% endfor %} + {% for announces in trackers %} + {% for tracker in announces %} +
+ {% if tracker not in trackers %} {{ tracker }} -
- {{ file.announce }} - - {% if file.announce not in trackers %} - + - + {% endif %} -
- {{ tracker }} - - {% if tracker not in trackers %} - - - - - - {% endif %} -
 
- {{ 'Locales'|trans }} -
 
- - {{'Edit'|trans }} - -
- - {{'Add'|trans }} - -
 
- {{ 'Pages'|trans }} -
 
- - {{'Edit'|trans }} - -
- - {{'Add'|trans }} - -
+
+
+
+ + + + + + {{ 'Pages'|trans }} +
+
+ {% for page in torrent.pages %} +
+ {{ page }} +
+ {% endfor %} +
+ + +
+
+
{% endblock %} \ No newline at end of file From 16696bc1d220e4d24455d46793d3f22a1f3526f8 Mon Sep 17 00:00:00 2001 From: ghost Date: Sun, 8 Oct 2023 01:13:15 +0300 Subject: [PATCH 135/434] increase margin --- templates/default/layout.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/default/layout.html.twig b/templates/default/layout.html.twig index 12c3d73..54900da 100644 --- a/templates/default/layout.html.twig +++ b/templates/default/layout.html.twig @@ -46,7 +46,7 @@
+ +