diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 3525e5f3..67a1e263 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -219,6 +219,11 @@ class User extends RowModel return $this->getRecord()->coins; } + function getRating(): int + { + return $this->getRecord()->rating; + } + function getReputation(): int { return $this->getRecord()->reputation; @@ -393,7 +398,8 @@ class User extends RowModel } return (object) [ - "total" => 100 - $incompleteness, + "total" => 100 - $incompleteness + $this->getRating(), + "percent" => min(100 - $incompleteness + $this->getRating(), 100), "unfilled" => $unfilled, ]; } diff --git a/Web/Models/Entities/Voucher.php b/Web/Models/Entities/Voucher.php new file mode 100644 index 00000000..6469dddc --- /dev/null +++ b/Web/Models/Entities/Voucher.php @@ -0,0 +1,85 @@ +getRecord()->coins; + } + + function getRating(): int + { + return $this->getRecord()->rating; + } + + function getToken(): string + { + return $this->getRecord()->token; + } + + function getFormattedToken(): string + { + $fmtTok = ""; + $token = $this->getRecord()->token; + foreach(array_chunk(str_split($token), 6) as $chunk) + $fmtTok .= implode("", $chunk) . "-"; + + return substr($fmtTok, 0, -1); + } + + function getRemainingUsages(): float + { + return (float) ($this->getRecord()->usages_left ?? INF); + } + + function getUsers(int $page = -1, ?int $perPage = NULL): \Traversable + { + $relations = $this->getRecord()->related("voucher_users.voucher"); + if($page !== -1) + $relations = $relations->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); + + foreach($relations as $relation) + yield (new Users)->get($relation->user); + } + + function isExpired(): bool + { + return $this->getRemainingUsages() < 1; + } + + function wasUsedBy(User $user): bool + { + $record = $this->getRecord()->related("voucher_users.voucher")->where("user", $user->getId()); + + return sizeof($record) > 0; + } + + function willUse(User $user): bool + { + if($this->wasUsedBy($user)) + return false; + + if($this->isExpired()) + return false; + + $this->setRemainingUsages($this->getRemainingUsages() - 1); + DB::i()->getContext()->table("voucher_users")->insert([ + "voucher" => $this->getId(), + "user" => $user->getId(), + ]); + + return true; + } + + function setRemainingUsages(float $usages): void + { + $this->stateChanges("usages_left", $usages === INF ? NULL : ((int) $usages)); + $this->save(); + } +} diff --git a/Web/Models/Repositories/Repository.php b/Web/Models/Repositories/Repository.php index 3d8e15e4..fe3d6afe 100644 --- a/Web/Models/Repositories/Repository.php +++ b/Web/Models/Repositories/Repository.php @@ -6,8 +6,8 @@ use Nette\Database\Table\ActiveRow; abstract class Repository { - private $context; - private $table; + protected $context; + protected $table; protected $tableName; protected $modelName; @@ -29,5 +29,18 @@ abstract class Repository return $this->toEntity($this->table->get($id)); } + function size(bool $withDeleted = false): int + { + return sizeof($this->table->where("deleted", $withDeleted)); + } + + function enumerate(int $page, ?int $perPage = NULL, bool $withDeleted = false): \Traversable + { + $perPage ??= OPENVK_DEFAULT_PER_PAGE; + + foreach($this->table->where("deleted", $withDeleted)->page($page, $perPage) as $entity) + yield $this->toEntity($entity); + } + use \Nette\SmartObject; } diff --git a/Web/Models/Repositories/Vouchers.php b/Web/Models/Repositories/Vouchers.php new file mode 100644 index 00000000..62d0a54e --- /dev/null +++ b/Web/Models/Repositories/Vouchers.php @@ -0,0 +1,19 @@ +table->where([ + "token" => $token, + "deleted" => $withDeleted, + ])->fetch(); + + return $this->toEntity($voucher); + } +} diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index 64620704..fe5dab25 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -1,17 +1,19 @@ users = $users; - $this->clubs = $clubs; + $this->users = $users; + $this->clubs = $clubs; + $this->vouchers = $vouchers; parent::__construct(); } @@ -106,6 +108,57 @@ final class AdminPresenter extends OpenVKPresenter } } + function renderVouchers(): void + { + $this->template->count = $this->vouchers->size(); + $this->template->vouchers = iterator_to_array($this->vouchers->enumerate((int) ($this->queryParam("p") ?? 1))); + } + + function renderVoucher(int $id): void + { + $voucher = NULL; + $this->template->form = (object) []; + if($id === 0) { + $this->template->form->id = 0; + $this->template->form->token = NULL; + $this->template->form->coins = 0; + $this->template->form->rating = 0; + $this->template->form->usages = -1; + $this->template->form->users = []; + } else { + $voucher = $this->vouchers->get($id); + if(!$voucher) + $this->notFound(); + + $this->template->form->id = $voucher->getId(); + $this->template->form->token = $voucher->getToken(); + $this->template->form->coins = $voucher->getCoins(); + $this->template->form->rating = $voucher->getRating(); + $this->template->form->usages = $voucher->getRemainingUsages(); + $this->template->form->users = iterator_to_array($voucher->getUsers()); + + if($this->template->form->usages === INF) + $this->template->form->usages = -1; + else + $this->template->form->usages = (int) $this->template->form->usages; + } + + if($_SERVER["REQUEST_METHOD"] !== "POST") + return; + + $voucher ??= new Voucher; + $voucher->setCoins((int) $this->postParam("coins")); + $voucher->setRating((int) $this->postParam("rating")); + $voucher->setRemainingUsages($this->postParam("usages") === '-1' ? INF : ((int) $this->postParam("usages"))); + if(!empty($tok = $this->postParam("token")) && strlen($tok) === 24) + $voucher->setToken($tok); + + $voucher->save(); + + $this->redirect("/admin/vouchers/id" . $voucher->getId(), static::REDIRECT_TEMPORARY); + exit; + } + function renderFiles(): void { diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 6b768c38..12245671 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -7,6 +7,7 @@ use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\Albums; use openvk\Web\Models\Repositories\Videos; use openvk\Web\Models\Repositories\Notes; +use openvk\Web\Models\Repositories\Vouchers; final class UserPresenter extends OpenVKPresenter { @@ -240,7 +241,7 @@ final class UserPresenter extends OpenVKPresenter if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc"))) $this->flashFail("err", tr("error"), tr("error_shorturl_incorrect")); - }elseif($_GET['act'] === "privacy") { + } else if($_GET['act'] === "privacy") { $settings = [ "page.read", "page.info.read", @@ -256,7 +257,22 @@ final class UserPresenter extends OpenVKPresenter $input = $this->postParam(str_replace(".", "_", $setting)); $user->setPrivacySetting($setting, min(3, abs($input ?? $user->getPrivacySetting($setting)))); } - }elseif($_GET['act'] === "interface") { + } else if($_GET['act'] === "finance.top-up") { + $token = $this->postParam("key0") . $this->postParam("key1") . $this->postParam("key2") . $this->postParam("key3"); + $voucher = (new Vouchers)->getByToken($token); + if(!$voucher) + $this->flashFail("err", tr("invalid_voucher"), tr("voucher_bad")); + + $perm = $voucher->willUse($user); + if(!$perm) + $this->flashFail("err", tr("invalid_voucher"), tr("voucher_bad")); + + $user->setCoins($user->getCoins() + $voucher->getCoins()); + $user->setRating($user->getRating() + $voucher->getRating()); + $user->save(); + + $this->flashFail("succ", tr("voucher_good"), tr("voucher_redeemed")); + } else if($_GET['act'] === "interface") { if (isset(Themepacks::i()[$this->postParam("style")]) || $this->postParam("style") === Themepacks::DEFAULT_THEME_ID) $user->setStyle($this->postParam("style")); @@ -271,7 +287,7 @@ final class UserPresenter extends OpenVKPresenter if(in_array($this->postParam("nsfw"), [0, 1, 2])) $user->setNsfwTolerance((int) $this->postParam("nsfw")); - }elseif($_GET['act'] === "lMenu") { + } else if($_GET['act'] === "lMenu") { $settings = [ "menu_bildoj" => "photos", "menu_filmetoj" => "videos", @@ -300,7 +316,7 @@ final class UserPresenter extends OpenVKPresenter ); } $this->template->mode = in_array($this->queryParam("act"), [ - "main", "privacy", "finance", "interface" + "main", "privacy", "finance", "finance.top-up", "interface" ]) ? $this->queryParam("act") : "main"; $this->template->user = $user; diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml index 9854e2f9..2fb177fe 100644 --- a/Web/Presenters/templates/Admin/@layout.xml +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -69,9 +69,14 @@ Группы + +
+ Платные услуги +
+ @@ -141,5 +146,9 @@ + + {script "js/node_modules/jquery/dist/jquery.min.js"} + {script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"} + diff --git a/Web/Presenters/templates/Admin/Voucher.xml b/Web/Presenters/templates/Admin/Voucher.xml new file mode 100644 index 00000000..163f6111 --- /dev/null +++ b/Web/Presenters/templates/Admin/Voucher.xml @@ -0,0 +1,92 @@ +{extends "@layout.xml"} + +{block title} + {_edit} +{/block} + +{block heading} + {_edit} №{$form->token ?? "undefined"} +{/block} + +{block content} +
+ + +
+
+
+ + +
+
+ + +
Номер состоит из 24 символов, если формат неправильный или поле не заполнено, будет назначен автоматически.
+
+
+ + +
+
+ + +
+
+ + +
Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет Infinity.
+
+ +
+
+ + +
+
+
+
+ +
+ + + + + + +
+ + + {$user->getCanonicalName()} + + + + {$user->getCanonicalName()} + + + заблокирован + +
+
+
+{/block} diff --git a/Web/Presenters/templates/Admin/Vouchers.xml b/Web/Presenters/templates/Admin/Vouchers.xml new file mode 100644 index 00000000..7ab5a32e --- /dev/null +++ b/Web/Presenters/templates/Admin/Vouchers.xml @@ -0,0 +1,61 @@ +{extends "@layout.xml"} + +{block title} + {_vouchers} +{/block} + +{block heading} + {_vouchers} +{/block} + +{block content} + + + + + + + + + + + + + + + + + + + + + + + +
#Серийный номерГолосаРейгтингОсталось использованийСостояниеДействия
{$voucher->getId()}{$voucher->getFormattedToken()}{$voucher->getCoins()}¢{$voucher->getRating()}{$voucher->getRemainingUsages() === INF ? "∞" : $voucher->getRemainingUsages()} + {if $voucher->isExpired()} + закончился + {else} + активен + {/if} + + + Редактировать + +
+
+ +
+ + {_create} + + + {var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count} + + ⭁ туда + + + ⭇ сюда + +
+{/block} diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index 08d6e897..812d8d5e 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -10,6 +10,7 @@ {var isMain = $mode === 'main'} {var isPrivacy = $mode === 'privacy'} {var isFinance = $mode === 'finance'} +{var isFinanceTU = $mode === 'finance.top-up'} {var isInterface = $mode === 'interface'}
@@ -19,8 +20,8 @@
{_"privacy"}
-
- {_points} +
{_"interface"} @@ -265,11 +266,26 @@ {_on_your_account}
{$thisUser->getCoins()}
- {_points_count} + {_points_count}

+ [{_have_voucher}?]

+ {elseif $isFinanceTU} + +

{_voucher_explanation} {_voucher_explanation_ex}

+
+ - + - + - + +

+ + + +
+ {elseif $isInterface}

{_ui_settings_interface}

diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml index 4df13723..5dd29366 100644 --- a/Web/Presenters/templates/User/View.xml +++ b/Web/Presenters/templates/User/View.xml @@ -121,7 +121,7 @@ {var completeness = $user->getProfileCompletenessReport()}
-
+
{$completeness->total}%
diff --git a/Web/di.yml b/Web/di.yml index a91d4f2a..5d239a2b 100644 --- a/Web/di.yml +++ b/Web/di.yml @@ -33,4 +33,5 @@ services: - openvk\Web\Models\Repositories\Notifications - openvk\Web\Models\Repositories\TicketComments - openvk\Web\Models\Repositories\IPs + - openvk\Web\Models\Repositories\Vouchers - openvk\Web\Models\Repositories\ContentSearchRepository diff --git a/Web/routes.yml b/Web/routes.yml index 5298b16f..f5598b94 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -209,6 +209,10 @@ routes: handler: "Admin->clubs" - url: "/admin/clubs/id{num}" handler: "Admin->club" + - url: "/admin/vouchers" + handler: "Admin->vouchers" + - url: "/admin/vouchers/id{num}" + handler: "Admin->voucher" - url: "/admin/ban.pl/{num}" handler: "Admin->quickBan" - url: "/admin/warn.pl/{num}"