mirror of
https://github.com/openvk/openvk
synced 2024-11-13 10:39:24 +03:00
Add gifts
Signed-off-by: Celestora <kitsuruko@gmail.com>
This commit is contained in:
parent
255d70e974
commit
9336a91623
26 changed files with 1367 additions and 80 deletions
164
Web/Models/Entities/Gift.php
Normal file
164
Web/Models/Entities/Gift.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use Nette\Utils\{Image, ImageException};
|
||||
|
||||
class Gift extends RowModel
|
||||
{
|
||||
const IMAGE_MAXSIZE = 131072;
|
||||
const IMAGE_BINARY = 0;
|
||||
const IMAGE_BASE64 = 1;
|
||||
const IMAGE_URL = 2;
|
||||
|
||||
const PERIOD_IGNORE = 0;
|
||||
const PERIOD_SET = 1;
|
||||
const PERIOD_SET_IF_NONE = 2;
|
||||
|
||||
protected $tableName = "gifts";
|
||||
|
||||
function getName(): string
|
||||
{
|
||||
return $this->getRecord()->internal_name;
|
||||
}
|
||||
|
||||
function getPrice(): int
|
||||
{
|
||||
return $this->getRecord()->price;
|
||||
}
|
||||
|
||||
function getUsages(): int
|
||||
{
|
||||
return $this->getRecord()->usages;
|
||||
}
|
||||
|
||||
function getUsagesBy(User $user, ?int $since = NULL): int
|
||||
{
|
||||
$sent = $this->getRecord()
|
||||
->related("gift_user_relations.gift")
|
||||
->where("sender", $user->getId())
|
||||
->where("sent >= ?", $since ?? $this->getRecord()->limit_period ?? 0);
|
||||
|
||||
return sizeof($sent);
|
||||
}
|
||||
|
||||
function getUsagesLeft(User $user): float
|
||||
{
|
||||
if($this->getLimit() === INF)
|
||||
return INF;
|
||||
|
||||
return max(0, $this->getLimit() - $this->getUsagesBy($user));
|
||||
}
|
||||
|
||||
function getImage(int $type = 0): /* ?binary */ string
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IMAGE_BINARY:
|
||||
return $this->getRecord()->image ?? "";
|
||||
break;
|
||||
case static::IMAGE_BASE64:
|
||||
return "data:image/png;base64," . base64_encode($this->getRecord()->image ?? "");
|
||||
break;
|
||||
case static::IMAGE_URL:
|
||||
return "/gift" . $this->getId() . "_" . $this->getUpdateDate()->timestamp() . ".png";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getLimit(): float
|
||||
{
|
||||
$limit = $this->getRecord()->limit;
|
||||
|
||||
return !$limit ? INF : (float) $limit;
|
||||
}
|
||||
|
||||
function getLimitResetTime(): ?DateTime
|
||||
{
|
||||
return is_null($t = $this->getRecord()->limit_period) ? NULL : new DateTime($t);
|
||||
}
|
||||
|
||||
function getUpdateDate(): DateTime
|
||||
{
|
||||
return new DateTime($this->getRecord()->updated);
|
||||
}
|
||||
|
||||
function canUse(User $user): bool
|
||||
{
|
||||
return $this->getUsagesLeft($user) > 0;
|
||||
}
|
||||
|
||||
function isFree(): bool
|
||||
{
|
||||
return $this->getPrice() === 0;
|
||||
}
|
||||
|
||||
function used(): void
|
||||
{
|
||||
$this->stateChanges("usages", $this->getUsages() + 1);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
function setName(string $name): void
|
||||
{
|
||||
$this->stateChanges("internal_name", $name);
|
||||
}
|
||||
|
||||
function setImage(string $file): bool
|
||||
{
|
||||
$imgBlob;
|
||||
try {
|
||||
$image = Image::fromFile($file);
|
||||
$image->resize(512, 512, Image::SHRINK_ONLY);
|
||||
|
||||
$imgBlob = $image->toString(Image::PNG);
|
||||
} catch(ImageException $ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(strlen($imgBlob) > (2**24 - 1)) {
|
||||
return false;
|
||||
} else {
|
||||
$this->stateChanges("updated", time());
|
||||
$this->stateChanges("image", $imgBlob);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function setLimit(?float $limit = NULL, int $periodBehaviour = 0): void
|
||||
{
|
||||
$limit ??= $this->getLimit();
|
||||
$limit = $limit === INF ? NULL : (int) $limit;
|
||||
$this->stateChanges("limit", $limit);
|
||||
|
||||
if(!$limit) {
|
||||
$this->stateChanges("limit_period", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
switch($periodBehaviour) {
|
||||
default:
|
||||
case static::PERIOD_IGNORE:
|
||||
break;
|
||||
|
||||
case static::PERIOD_SET:
|
||||
$this->stateChanges("limit_period", time());
|
||||
break;
|
||||
|
||||
case static::PERIOD_SET_IF_NONE:
|
||||
if(is_null($this->getRecord()) || is_null($this->getRecord()->limit_period))
|
||||
$this->stateChanges("limit_period", time());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function delete(bool $softly = true): void
|
||||
{
|
||||
$this->getRecord()->related("gift_relations.gift")->delete();
|
||||
|
||||
parent::delete($softly);
|
||||
}
|
||||
}
|
155
Web/Models/Entities/GiftCategory.php
Normal file
155
Web/Models/Entities/GiftCategory.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\Web\Models\Repositories\Gifts;
|
||||
use openvk\Web\Models\RowModel;
|
||||
|
||||
class GiftCategory extends RowModel
|
||||
{
|
||||
protected $tableName = "gift_categories";
|
||||
|
||||
private function getLocalization(string $language): object
|
||||
{
|
||||
return $this->getRecord()
|
||||
->related("gift_categories_locales.category")
|
||||
->where("language", $language);
|
||||
}
|
||||
|
||||
private function createLocalizationIfNotExists(string $language): void
|
||||
{
|
||||
if(!is_null($this->getLocalization($language)->fetch()))
|
||||
return;
|
||||
|
||||
DB::i()->getContext()->table("gift_categories_locales")->insert([
|
||||
"category" => $this->getId(),
|
||||
"language" => $language,
|
||||
"name" => "Sample Text",
|
||||
"description" => "Sample Text",
|
||||
]);
|
||||
}
|
||||
|
||||
function getSlug(): string
|
||||
{
|
||||
return \Transliterator::createFromRules(
|
||||
":: Any-Latin;"
|
||||
. ":: NFD;"
|
||||
. ":: [:Nonspacing Mark:] Remove;"
|
||||
. ":: NFC;"
|
||||
. ":: [:Punctuation:] Remove;"
|
||||
. ":: Lower();"
|
||||
. "[:Separator:] > '-'"
|
||||
)->transliterate($this->getName());
|
||||
}
|
||||
|
||||
function getThumbnailURL(): string
|
||||
{
|
||||
$primeGift = iterator_to_array($this->getGifts(1, 1))[0];
|
||||
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
|
||||
if(!$primeGift)
|
||||
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
|
||||
|
||||
return $primeGift->getImage(Gift::IMAGE_URL);
|
||||
}
|
||||
|
||||
function getName(string $language = "_", bool $returnNull = false): ?string
|
||||
{
|
||||
$loc = $this->getLocalization($language)->fetch();
|
||||
if(!$loc) {
|
||||
if($returnNull)
|
||||
return NULL;
|
||||
|
||||
return $language === "_" ? "Unlocalized" : $this->getName();
|
||||
}
|
||||
|
||||
return $loc->name;
|
||||
}
|
||||
|
||||
function getDescription(string $language = "_", bool $returnNull = false): ?string
|
||||
{
|
||||
$loc = $this->getLocalization($language)->fetch();
|
||||
if(!$loc) {
|
||||
if($returnNull)
|
||||
return NULL;
|
||||
|
||||
return $language === "_" ? "Unlocalized" : $this->getDescription();
|
||||
}
|
||||
|
||||
return $loc->description;
|
||||
}
|
||||
|
||||
function getGifts(int $page = -1, ?int $perPage = NULL, &$count = nullptr): \Traversable
|
||||
{
|
||||
$gifts = $this->getRecord()->related("gift_relations.category");
|
||||
if($page !== -1) {
|
||||
$count = $gifts->count();
|
||||
$gifts = $gifts->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
}
|
||||
|
||||
foreach($gifts as $rel)
|
||||
yield (new Gifts)->get($rel->gift);
|
||||
}
|
||||
|
||||
function isMagical(): bool
|
||||
{
|
||||
return !is_null($this->getRecord()->autoquery);
|
||||
}
|
||||
|
||||
function hasGift(Gift $gift): bool
|
||||
{
|
||||
$rels = $this->getRecord()->related("gift_relations.category");
|
||||
|
||||
return $rels->where("gift", $gift->getId())->count() > 0;
|
||||
}
|
||||
|
||||
function addGift(Gift $gift): void
|
||||
{
|
||||
if($this->hasGift($gift))
|
||||
return;
|
||||
|
||||
DB::i()->getContext()->table("gift_relations")->insert([
|
||||
"category" => $this->getId(),
|
||||
"gift" => $gift->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
function removeGift(Gift $gift): void
|
||||
{
|
||||
if(!$this->hasGift($gift))
|
||||
return;
|
||||
|
||||
DB::i()->getContext()->table("gift_relations")->where([
|
||||
"category" => $this->getId(),
|
||||
"gift" => $gift->getId(),
|
||||
])->delete();
|
||||
}
|
||||
|
||||
function setName(string $language, string $name): void
|
||||
{
|
||||
$this->createLocalizationIfNotExists($language);
|
||||
$this->getLocalization($language)->update([
|
||||
"name" => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
function setDescription(string $language, string $description): void
|
||||
{
|
||||
$this->createLocalizationIfNotExists($language);
|
||||
$this->getLocalization($language)->update([
|
||||
"description" => $description,
|
||||
]);
|
||||
}
|
||||
|
||||
function setAutoQuery(?array $query = NULL): void
|
||||
{
|
||||
if(is_null($query)) {
|
||||
$this->stateChanges("autoquery", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
$allowedColumns = ["price", "usages"];
|
||||
if(array_diff_key($query, array_flip($allowedColumns)))
|
||||
throw new \LogicException("Invalid query");
|
||||
|
||||
$this->stateChanges("autoquery", serialize($query));
|
||||
}
|
||||
}
|
13
Web/Models/Entities/Notifications/GiftNotification.php
Normal file
13
Web/Models/Entities/Notifications/GiftNotification.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities\Notifications;
|
||||
use openvk\Web\Models\Entities\{User, Gift};
|
||||
|
||||
final class GiftNotification extends Notification
|
||||
{
|
||||
protected $actionCode = 9601;
|
||||
|
||||
function __construct(User $receiver, User $sender, Gift $gift, ?string $comment)
|
||||
{
|
||||
parent::__construct($receiver, $gift, $sender, time(), $comment ?? "");
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ namespace openvk\Web\Models\Entities;
|
|||
use openvk\Web\Themes\{Themepack, Themepacks};
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\{Photo, Message, Correspondence};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Notifications};
|
||||
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Gifts, Notifications};
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use Chandler\Security\User as ChandlerUser;
|
||||
|
@ -474,6 +474,25 @@ class User extends RowModel
|
|||
return sizeof($this->getRecord()->related("event_turnouts.user"));
|
||||
}
|
||||
|
||||
function getGifts(int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$gifts = $this->getRecord()->related("gift_user_relations.receiver")->order("sent DESC")->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
foreach($gifts as $rel) {
|
||||
yield (object) [
|
||||
"sender" => (new Users)->get($rel->sender),
|
||||
"gift" => (new Gifts)->get($rel->gift),
|
||||
"caption" => $rel->comment,
|
||||
"anon" => $rel->anonymous,
|
||||
"sent" => new DateTime($rel->sent),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function getGiftCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("gift_user_relations.receiver"));
|
||||
}
|
||||
|
||||
function getSubscriptionStatus(User $user): int
|
||||
{
|
||||
$subbed = !is_null($this->getRecord()->related("subscriptions.follower")->where([
|
||||
|
@ -544,6 +563,18 @@ class User extends RowModel
|
|||
return !is_null($this->getPendingPhoneVerification());
|
||||
}
|
||||
|
||||
function gift(User $sender, Gift $gift, ?string $comment = NULL, bool $anonymous = false): void
|
||||
{
|
||||
DatabaseConnection::i()->getContext()->table("gift_user_relations")->insert([
|
||||
"sender" => $sender->getId(),
|
||||
"receiver" => $this->getId(),
|
||||
"gift" => $gift->getId(),
|
||||
"comment" => $comment,
|
||||
"anonymous" => $anonymous,
|
||||
"sent" => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
function ban(string $reason): void
|
||||
{
|
||||
$subs = DatabaseConnection::i()->getContext()->table("subscriptions");
|
||||
|
|
45
Web/Models/Repositories/Gifts.php
Normal file
45
Web/Models/Repositories/Gifts.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use openvk\Web\Models\Entities\{Gift, GiftCategory};
|
||||
|
||||
class Gifts
|
||||
{
|
||||
private $context;
|
||||
private $gifts;
|
||||
private $cats;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->context = DatabaseConnection::i()->getContext();
|
||||
$this->gifts = $this->context->table("gifts");
|
||||
$this->cats = $this->context->table("gift_categories");
|
||||
}
|
||||
|
||||
function get(int $id): ?Gift
|
||||
{
|
||||
$gift = $this->gifts->get($id);
|
||||
if(!$gift)
|
||||
return NULL;
|
||||
|
||||
return new Gift($gift);
|
||||
}
|
||||
|
||||
function getCat(int $id): ?GiftCategory
|
||||
{
|
||||
$cat = $this->cats->get($id);
|
||||
if(!$cat)
|
||||
return NULL;
|
||||
|
||||
return new GiftCategory($cat);
|
||||
}
|
||||
|
||||
function getCategories(int $page, ?int $perPage = NULL, &$count = nullptr): \Traversable
|
||||
{
|
||||
$cats = $this->cats->where("deleted", false);
|
||||
$count = $cats->count();
|
||||
$cats = $cats->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
foreach($cats as $cat)
|
||||
yield new GiftCategory($cat);
|
||||
}
|
||||
}
|
|
@ -1,19 +1,21 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Voucher, User};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Vouchers};
|
||||
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Vouchers, Gifts};
|
||||
|
||||
final class AdminPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $users;
|
||||
private $clubs;
|
||||
private $vouchers;
|
||||
private $gifts;
|
||||
|
||||
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers)
|
||||
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts)
|
||||
{
|
||||
$this->users = $users;
|
||||
$this->clubs = $clubs;
|
||||
$this->vouchers = $vouchers;
|
||||
$this->gifts = $gifts;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
@ -159,6 +161,156 @@ final class AdminPresenter extends OpenVKPresenter
|
|||
exit;
|
||||
}
|
||||
|
||||
function renderGiftCategories(): void
|
||||
{
|
||||
$this->template->act = $this->queryParam("act") ?? "list";
|
||||
$this->template->categories = iterator_to_array($this->gifts->getCategories((int) ($this->queryParam("p") ?? 1), NULL, $this->template->count));
|
||||
}
|
||||
|
||||
function renderGiftCategory(string $slug, int $id): void
|
||||
{
|
||||
$cat;
|
||||
$gen = false;
|
||||
if($id !== 0) {
|
||||
$cat = $this->gifts->getCat($id);
|
||||
if(!$cat)
|
||||
$this->notFound();
|
||||
else if($cat->getSlug() !== $slug)
|
||||
$this->redirect("/admin/gifts/" . $cat->getSlug() . "." . $id . ".meta", static::REDIRECT_TEMPORARY);
|
||||
} else {
|
||||
$gen = true;
|
||||
$cat = new GiftCategory;
|
||||
}
|
||||
|
||||
$this->template->form = (object) [];
|
||||
$this->template->form->id = $id;
|
||||
$this->template->form->languages = [];
|
||||
foreach(getLanguages() as $language) {
|
||||
$language = (object) $language;
|
||||
$this->template->form->languages[$language->code] = (object) [];
|
||||
|
||||
$this->template->form->languages[$language->code]->name = $gen ? "" : ($cat->getName($language->code, true) ?? "");
|
||||
$this->template->form->languages[$language->code]->description = $gen ? "" : ($cat->getDescription($language->code, true) ?? "");
|
||||
}
|
||||
|
||||
$this->template->form->languages["master"] = (object) [
|
||||
"name" => $gen ? "Unknown Name" : $cat->getName(),
|
||||
"description" => $gen ? "" : $cat->getDescription(),
|
||||
];
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST")
|
||||
return;
|
||||
|
||||
if($gen) {
|
||||
$cat->setAutoQuery(NULL);
|
||||
$cat->save();
|
||||
}
|
||||
|
||||
$cat->setName("_", $this->postParam("name_master"));
|
||||
$cat->setDescription("_", $this->postParam("description_master"));
|
||||
foreach(getLanguages() as $language) {
|
||||
$code = $language["code"];
|
||||
if(!empty($this->postParam("name_$code") ?? NULL))
|
||||
$cat->setName($code, $this->postParam("name_$code"));
|
||||
|
||||
if(!empty($this->postParam("description_$code") ?? NULL))
|
||||
$cat->setDescription($code, $this->postParam("description_$code"));
|
||||
}
|
||||
|
||||
$this->redirect("/admin/gifts/" . $cat->getSlug() . "." . $cat->getId() . ".meta", static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
function renderGifts(string $catSlug, int $catId): void
|
||||
{
|
||||
$cat = $this->gifts->getCat($catId);
|
||||
if(!$cat)
|
||||
$this->notFound();
|
||||
else if($cat->getSlug() !== $catSlug)
|
||||
$this->redirect("/admin/gifts/" . $cat->getSlug() . "." . $catId . "/", static::REDIRECT_TEMPORARY);
|
||||
|
||||
$this->template->cat = $cat;
|
||||
$this->template->gifts = iterator_to_array($cat->getGifts((int) ($this->queryParam("p") ?? 1), NULL, $this->template->count));
|
||||
}
|
||||
|
||||
function renderGift(int $id): void
|
||||
{
|
||||
$gift = $this->gifts->get($id);
|
||||
$act = $this->queryParam("act") ?? "edit";
|
||||
switch($act) {
|
||||
case "delete":
|
||||
$this->assertNoCSRF();
|
||||
if(!$gift)
|
||||
$this->notFound();
|
||||
|
||||
$gift->delete();
|
||||
$this->flashFail("succ", "Gift moved successfully", "This gift will now be in <b>Recycle Bin</b>.");
|
||||
break;
|
||||
case "copy":
|
||||
case "move":
|
||||
$this->assertNoCSRF();
|
||||
if(!$gift)
|
||||
$this->notFound();
|
||||
|
||||
$catFrom = $this->gifts->getCat((int) ($this->queryParam("from") ?? 0));
|
||||
$catTo = $this->gifts->getCat((int) ($this->queryParam("to") ?? 0));
|
||||
if(!$catFrom || !$catTo || !$catFrom->hasGift($gift))
|
||||
$this->badRequest();
|
||||
|
||||
if($act === "move")
|
||||
$catFrom->removeGift($gift);
|
||||
|
||||
$catTo->addGift($gift);
|
||||
|
||||
$name = $catTo->getName();
|
||||
$this->flash("succ", "Gift moved successfully", "This gift will now be in <b>$name</b>.");
|
||||
$this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/", static::REDIRECT_TEMPORARY);
|
||||
break;
|
||||
default:
|
||||
case "edit":
|
||||
$gen = false;
|
||||
if(!$gift) {
|
||||
$gen = true;
|
||||
$gift = new Gift;
|
||||
}
|
||||
|
||||
$this->template->form = (object) [];
|
||||
$this->template->form->id = $id;
|
||||
$this->template->form->name = $gen ? "New Gift (1)" : $gift->getName();
|
||||
$this->template->form->price = $gen ? 0 : $gift->getPrice();
|
||||
$this->template->form->usages = $gen ? 0 : $gift->getUsages();
|
||||
$this->template->form->limit = $gen ? -1 : ($gift->getLimit() === INF ? -1 : $gift->getLimit());
|
||||
$this->template->form->pic = $gen ? NULL : $gift->getImage(Gift::IMAGE_URL);
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST")
|
||||
return;
|
||||
|
||||
$limit = $this->postParam("limit") ?? $this->template->form->limit;
|
||||
$limit = $limit == "-1" ? INF : (float) $limit;
|
||||
$gift->setLimit($limit, is_null($this->postParam("reset_limit")) ? Gift::PERIOD_SET_IF_NONE : Gift::PERIOD_SET);
|
||||
|
||||
$gift->setName($this->postParam("name"));
|
||||
$gift->setPrice((int) $this->postParam("price"));
|
||||
$gift->setUsages((int) $this->postParam("usages"));
|
||||
if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) {
|
||||
if(!$gift->setImage($_FILES["pic"]["tmp_name"]))
|
||||
$this->flashFail("err", "Не удалось сохранить подарок", "Изображение подарка кривое.");
|
||||
} else if($gen) {
|
||||
# If there's no gift pic but it's newly created
|
||||
$this->flashFail("err", "Не удалось сохранить подарок", "Пожалуйста, загрузите изображение подарка.");
|
||||
}
|
||||
|
||||
$gift->save();
|
||||
|
||||
if($gen && !is_null($cat = $this->postParam("_cat"))) {
|
||||
$cat = $this->gifts->getCat((int) $cat);
|
||||
if(!is_null($cat))
|
||||
$cat->addGift($gift);
|
||||
}
|
||||
|
||||
$this->redirect("/admin/gifts/id" . $gift->getId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
}
|
||||
|
||||
function renderFiles(): void
|
||||
{
|
||||
|
||||
|
|
131
Web/Presenters/GiftsPresenter.php
Normal file
131
Web/Presenters/GiftsPresenter.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Repositories\{Gifts, Users};
|
||||
use openvk\Web\Models\Entities\Notifications\GiftNotification;
|
||||
|
||||
final class GiftsPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $gifts;
|
||||
private $users;
|
||||
|
||||
function __construct(Gifts $gifts, Users $users)
|
||||
{
|
||||
$this->gifts = $gifts;
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
function renderUserGifts(int $user): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$user = $this->users->get($user);
|
||||
if(!$user)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
|
||||
$this->template->count = $user->getGiftCount();
|
||||
$this->template->iterator = $user->getGifts($page);
|
||||
$this->template->hideInfo = $this->user->id !== $user->getId();
|
||||
}
|
||||
|
||||
function renderGiftMenu(): void
|
||||
{
|
||||
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
|
||||
if(!$user)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
|
||||
$cats = $this->gifts->getCategories($page, NULL, $this->template->count);
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->iterator = $cats;
|
||||
$this->template->_template = "Gifts/Menu.xml";
|
||||
}
|
||||
|
||||
function renderGiftList(): void
|
||||
{
|
||||
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
|
||||
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
|
||||
if(!$user || !$cat)
|
||||
$this->flashFail("err", "Не удалось подарить", "Пользователь или набор не существуют.");
|
||||
|
||||
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
|
||||
$gifts = $cat->getGifts($page, null, $this->template->count);
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->cat = $cat;
|
||||
$this->template->gifts = iterator_to_array($gifts);
|
||||
$this->template->_template = "Gifts/Pick.xml";
|
||||
}
|
||||
|
||||
function renderConfirmGift(): void
|
||||
{
|
||||
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
|
||||
$gift = $this->gifts->get((int) ($this->queryParam("elid") ?? 0));
|
||||
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
|
||||
if(!$user || !$cat || !$gift || !$cat->hasGift($gift))
|
||||
$this->flashFail("err", "Не удалось подарить", "Не удалось подтвердить права на подарок.");
|
||||
|
||||
if(!$gift->canUse($this->user->identity))
|
||||
$this->flashFail("err", "Не удалось подарить", "У вас больше не осталось таких подарков.");
|
||||
|
||||
$coinsLeft = $this->user->identity->getCoins() - $gift->getPrice();
|
||||
if($coinsLeft < 0)
|
||||
$this->flashFail("err", "Не удалось подарить", "Ору нищ не пук.");
|
||||
|
||||
$this->template->_template = "Gifts/Confirm.xml";
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
$this->template->user = $user;
|
||||
$this->template->cat = $cat;
|
||||
$this->template->gift = $gift;
|
||||
return;
|
||||
}
|
||||
|
||||
$comment = empty($c = $this->postParam("comment")) ? NULL : $c;
|
||||
$notification = new GiftNotification($user, $this->user->identity, $gift, $comment);
|
||||
$notification->emit();
|
||||
$this->user->identity->setCoins($coinsLeft);
|
||||
$user->gift($this->user->identity, $gift, $comment, !is_null($this->postParam("anonymous")));
|
||||
$gift->used();
|
||||
|
||||
$this->flash("succ", "Подарок отправлен", "Вы отправили подарок <b>" . $user->getFirstName() . "</b> за " . $gift->getPrice() . " голосов.");
|
||||
$this->redirect($user->getURL(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
function renderStub(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$act = $this->queryParam("act");
|
||||
switch($act) {
|
||||
case "pick":
|
||||
$this->renderGiftMenu();
|
||||
break;
|
||||
|
||||
case "menu":
|
||||
$this->renderGiftList();
|
||||
break;
|
||||
|
||||
case "confirm":
|
||||
$this->renderConfirmGift();
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->notFound();
|
||||
}
|
||||
}
|
||||
|
||||
function renderGiftImage(int $id, int $timestamp): void
|
||||
{
|
||||
$gift = $this->gifts->get($id);
|
||||
if(!$gift)
|
||||
$this->notFound();
|
||||
|
||||
$image = $gift->getImage();
|
||||
header("Cache-Control: no-transform, immutable");
|
||||
header("Content-Length: " . strlen($image));
|
||||
header("Content-Type: image/png");
|
||||
exit($image);
|
||||
}
|
||||
}
|
|
@ -1,57 +1,61 @@
|
|||
<div class="ovk-lw-container">
|
||||
<div class="ovk-lw--list">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
{if sizeof($data) > 0}
|
||||
<table n:foreach="$data as $dat" border="0" style="font-size:11px;" class="post">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
{include preview, x => $dat}
|
||||
</td>
|
||||
|
||||
<td width="345" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{include link, x => $dat}">
|
||||
<b>
|
||||
{include name, x => $dat}
|
||||
</b>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="post-content" style="padding: 4px;font-size: 11px;">
|
||||
{include description, x => $dat}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
{extends "@layout.xml"}
|
||||
|
||||
{block wrap}
|
||||
<div class="ovk-lw-container">
|
||||
<div class="ovk-lw--list">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
<div style="padding: 8px;">
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
]}
|
||||
</div>
|
||||
{else}
|
||||
{ifset customErrorMessage}
|
||||
{include customErrorMessage}
|
||||
{if sizeof($data) > 0}
|
||||
<table n:foreach="$data as $dat" border="0" style="font-size:11px;" class="post">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
{include preview, x => $dat}
|
||||
</td>
|
||||
|
||||
<td width="345" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{include link, x => $dat}">
|
||||
<b>
|
||||
{include name, x => $dat}
|
||||
</b>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="post-content" style="padding: 4px;font-size: 11px;">
|
||||
{include description, x => $dat}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
<div style="padding: 8px;">
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
]}
|
||||
</div>
|
||||
{else}
|
||||
{include "components/nothing.xml"}
|
||||
{/ifset}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="ovk-lw--actions">
|
||||
{include actions}
|
||||
<hr/>
|
||||
<div n:if="$sorting ?? true" class="tile">
|
||||
<a href="?C=I;O=R" class="profile_link">{_"sort_randomly"}</a>
|
||||
<a href="?C=M;O=D" class="profile_link">{_"sort_up"}</a>
|
||||
<a href="?C=M;O=A" class="profile_link">{_"sort_down"}</a>
|
||||
{ifset customErrorMessage}
|
||||
{include customErrorMessage}
|
||||
{else}
|
||||
{include "components/nothing.xml"}
|
||||
{/ifset}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="ovk-lw--actions">
|
||||
{include actions}
|
||||
<hr/>
|
||||
<div n:if="$sorting ?? true" class="tile">
|
||||
<a href="?C=I;O=R" class="profile_link">{_"sort_randomly"}</a>
|
||||
<a href="?C=M;O=D" class="profile_link">{_"sort_up"}</a>
|
||||
<a href="?C=M;O=A" class="profile_link">{_"sort_down"}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -268,6 +268,10 @@
|
|||
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['enable']"
|
||||
async defer data-domain="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['domain']}"
|
||||
src="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['server']}js/plausible.js"></script>
|
||||
|
||||
{ifset bodyScripts}
|
||||
{include bodyScripts}
|
||||
{/ifset}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -79,6 +79,11 @@
|
|||
{_vouchers}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/gifts">
|
||||
Подарки
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Настройки</strong>
|
||||
|
@ -125,10 +130,24 @@
|
|||
</nav>
|
||||
</div>
|
||||
<section class="aui-page-panel-content">
|
||||
{ifset $flashMessage}
|
||||
{var type = ["err" => "error", "warn" => "warning", "info" => "basic", "succ" => "success"][$flashMessage->type]}
|
||||
<div class="aui-message aui-message-{$type}" style="margin-bottom: 15px;">
|
||||
<p class="title">
|
||||
<strong>{$flashMessage->title}</strong>
|
||||
</p>
|
||||
<p>{$flashMessage->msg|noescape}</p>
|
||||
</div>
|
||||
{/ifset}
|
||||
|
||||
<header class="aui-page-header">
|
||||
<div class="aui-page-header-inner">
|
||||
<div class="aui-page-header-main">
|
||||
<h1>{include heading}</h1>
|
||||
{ifset headingWrap}
|
||||
{include headingWrap}
|
||||
{else}
|
||||
<h1>{include heading}</h1>
|
||||
{/ifset}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -150,5 +169,9 @@
|
|||
{script "js/node_modules/jquery/dist/jquery.min.js"}
|
||||
{script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"}
|
||||
<script>AJS.tabs.setup();</script>
|
||||
|
||||
{ifset scripts}
|
||||
{include scripts}
|
||||
{/ifset}
|
||||
</body>
|
||||
</html>
|
||||
|
|
106
Web/Presenters/templates/Admin/Gift.xml
Normal file
106
Web/Presenters/templates/Admin/Gift.xml
Normal file
|
@ -0,0 +1,106 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{if $form->id === 0}
|
||||
Новый подарок
|
||||
{else}
|
||||
Подарок "{$form->name}"
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form class="aui" method="POST" enctype="multipart/form-data">
|
||||
<div class="field-group">
|
||||
<label for="avatar">
|
||||
Изображение
|
||||
<span n:if="$form->id === 0" class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
{if $form->id === 0}
|
||||
<input type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" required="required" />
|
||||
{else}
|
||||
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
|
||||
<span class="aui-avatar-inner">
|
||||
<img id="pic" src="{$form->pic}" style="object-fit: cover;"></img>
|
||||
</span>
|
||||
</span>
|
||||
<input style="display: none;" id="picInput" type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" />
|
||||
<div class="description">
|
||||
<a id="picChange" href="javascript:false">Заменить изображение?</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="id" disabled="disabled" value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="putin">
|
||||
Использований
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="putin" disabled="disabled" value="{$form->usages}" />
|
||||
<div n:if="$form->usages > 0" class="description">
|
||||
<a href="javascript:$('#putin').value(0);">Обнулить?</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name">
|
||||
Внутренее имя
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name" name="name" value="{$form->name}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="price">
|
||||
Цена
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="price" name="price" min="0" value="{$form->price}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="limit">
|
||||
Ограничение
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="-1" id="limit" name="limit" value="{$form->limit}" />
|
||||
</div>
|
||||
<fieldset class="group">
|
||||
<legend></legend>
|
||||
<div class="checkbox" resolved="">
|
||||
<input n:attr="disabled => $form->id === 0, checked => $form->id === 0" class="checkbox" type="checkbox" name="reset_limit" id="reset_limit" />
|
||||
<span class="aui-form-glyph"></span>
|
||||
|
||||
<label for="reset_limit">Сбросить счётчик ограничений</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<input n:if="$form->id === 0" type="hidden" name="_cat" value="{$_GET['cat'] ?? 1}" />
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="aui-button aui-button-primary submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
||||
|
||||
{block scripts}
|
||||
<script>
|
||||
const TRANS_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
|
||||
$("#picChange").click(_ => $("#picInput").click());
|
||||
$("#picInput").bind("change", e => {
|
||||
if(typeof e.target.files[0] === "undefined")
|
||||
$("#pic").prop("src", URL.createObjectURL(TRANS_GIF));
|
||||
|
||||
$("#pic").prop("src", URL.createObjectURL(e.target.files[0]));
|
||||
});
|
||||
</script>
|
||||
{/block}
|
56
Web/Presenters/templates/Admin/GiftCategories.xml
Normal file
56
Web/Presenters/templates/Admin/GiftCategories.xml
Normal file
|
@ -0,0 +1,56 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Наборы подарков
|
||||
{/block}
|
||||
|
||||
{block headingWrap}
|
||||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/gifts/new.0.meta">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>Наборы подарков</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div id="spacer" style="height: 8px;"></div>
|
||||
{if sizeof($categories) > 0}
|
||||
<table class="aui aui-table-list">
|
||||
<tbody>
|
||||
<tr n:foreach="$categories as $cat">
|
||||
<td style="vertical-align: middle;">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-folder-filled">{$cat->getName()}</span>
|
||||
{$cat->getName()}
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{ovk_proc_strtr($cat->getDescription(), 128)}
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<a class="aui-button aui-button-primary" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}.meta">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
</a>
|
||||
|
||||
<a class="aui-button" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}/">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-gallery">Открыть</span>
|
||||
Открыть
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{else}
|
||||
<center>
|
||||
<p>Наборов подарков нету. Чтобы создать подарок, создайте набор.</p>
|
||||
</center>
|
||||
{/if}
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
</div>
|
||||
{/block}
|
72
Web/Presenters/templates/Admin/GiftCategory.xml
Normal file
72
Web/Presenters/templates/Admin/GiftCategory.xml
Normal file
|
@ -0,0 +1,72 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{if $form->id === 0}
|
||||
Создать набор подарков
|
||||
{else}
|
||||
{$form->languages["master"]->name}
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form class="aui" method="POST">
|
||||
<h3>Общие настройки</h3>
|
||||
<fieldset>
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="id" name="id" disabled value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name_master">
|
||||
Наименование
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name_master" name="name_master" value="{$form->languages['master']->name}" />
|
||||
<div class="description">Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="description_master">
|
||||
Описание
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="description_master" name="description_master" value="{$form->languages['master']->description}" />
|
||||
<div class="description">Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<h3>Языко-зависимые настройки</h3>
|
||||
<fieldset>
|
||||
{foreach $form->languages as $locale => $data}
|
||||
{continueIf $locale === "master"}
|
||||
|
||||
<div class="field-group">
|
||||
<label for="name_{$locale}">
|
||||
Наименование
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name_{$locale}" name="name_{$locale}" value="{$data->name}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="description_{$locale}">
|
||||
Описание
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="description_{$locale}" name="description_{$locale}" value="{$data->description}" />
|
||||
</div>
|
||||
{/foreach}
|
||||
</fieldset>
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="aui-button aui-button-primary submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
82
Web/Presenters/templates/Admin/Gifts.xml
Normal file
82
Web/Presenters/templates/Admin/Gifts.xml
Normal file
|
@ -0,0 +1,82 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{$cat->getName()}
|
||||
{/block}
|
||||
|
||||
{block headingWrap}
|
||||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/gifts/id0?act=edit&cat={$cat->getId()}">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>Набор "{$cat->getName()}"</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{if sizeof($gifts) > 0}
|
||||
<table class="aui aui-table-list">
|
||||
<thead>
|
||||
<th>Подарок</th>
|
||||
<th>Имя</th>
|
||||
<th>Цена</th>
|
||||
<th>Подарен</th>
|
||||
<th>Ограничение</th>
|
||||
<th>Сброс счётчика ограничений</th>
|
||||
<th>Действия</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr n:foreach="$gifts as $gift">
|
||||
<td style="vertical-align: middle; width: 0px;">
|
||||
<img style="max-width: 32px;" src="{$gift->getImage(2)}" alt="{$gift->getName()}" />
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getName()}
|
||||
<span n:if="$gift->isFree()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">
|
||||
бесплатный
|
||||
</span>
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getPrice()} голосов
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getUsages()} раз
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{if $gift->getLimit() === INF}
|
||||
Отсутствует
|
||||
{else}
|
||||
Не более {$gift->getLimit()} дарений
|
||||
{/if}
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{if !$gift->getLimitResetTime()}
|
||||
Никогда
|
||||
{else}
|
||||
Последний раз в
|
||||
{$gift->getLimitResetTime()->format("%a, %d %B %G")}
|
||||
{/if}
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<a class="aui-button aui-button-primary" href="/admin/gifts/id{$gift->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{else}
|
||||
<center>
|
||||
<p>Подарков нету. Нажмите на красивую кнопку вверху, чтобы создать первый.</p>
|
||||
</center>
|
||||
{/if}
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
</div>
|
||||
{/block}
|
|
@ -4,8 +4,12 @@
|
|||
{_vouchers}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{_vouchers}
|
||||
{block headingWrap}
|
||||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/vouchers/id0">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>{_vouchers}</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
|
@ -46,10 +50,6 @@
|
|||
<br/>
|
||||
|
||||
<div align="right">
|
||||
<a style="float: left;" class="aui-button aui-button-primary" href="/admin/vouchers/id0">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
|
|
30
Web/Presenters/templates/Gifts/Confirm.xml
Normal file
30
Web/Presenters/templates/Gifts/Confirm.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Подарить подарок
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">Выбор подарка</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">Коллекции</a> »
|
||||
<a href="/gifts?act=menu&user={$user->getId()}&pack={$cat->getId()}">{$cat->getName(tr("__lang"))}</a> »
|
||||
Подтверждение
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<center>
|
||||
<img class="gift_confirm_pic" style="max-width: 256px;" src="{$gift->getImage(2)}" alt="Подарок" />
|
||||
|
||||
<form style="width: 65%;" method="POST">
|
||||
<textarea name="comment" style="resize: vertical; height: 65px;" placeholder="Ваше сообщение, которое будет доставлено вместе с подарком"></textarea>
|
||||
<br/><br/>
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="Отправить" class="button" />
|
||||
<label>
|
||||
<input type="checkbox" name="anonymous"> Анонимно
|
||||
</label>
|
||||
</form>
|
||||
</center>
|
||||
{/block}
|
29
Web/Presenters/templates/Gifts/Menu.xml
Normal file
29
Web/Presenters/templates/Gifts/Menu.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
{extends "../@listView.xml"}
|
||||
|
||||
{block title}
|
||||
Выбрать подарок
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">Выбор подарка</a> »
|
||||
Коллекции
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
/gifts?act=menu&user={$user->getId()}&pack={$x->getId()}
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getThumbnailURL()}" width="75" alt="{$x->getName(tr('__lang'))}" />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
{$x->getName(tr("__lang"))}
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
{$x->getDescription(tr("__lang"))}
|
||||
{/block}
|
58
Web/Presenters/templates/Gifts/Pick.xml
Normal file
58
Web/Presenters/templates/Gifts/Pick.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Выбрать подарок
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">Выбор подарка</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">Коллекции</a> »
|
||||
{$cat->getName(tr("__lang"))}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="gift_grid">
|
||||
<div n:foreach="$gifts as $gift" n:class="gift_sel, !$gift->canUse($thisUser) ? disabled" data-gift="{$gift->getId()}">
|
||||
<img class="gift_pic" src="{$gift->getImage(2)}" alt="Подарок" />
|
||||
|
||||
<strong class="gift_price">
|
||||
{if $gift->isFree()}
|
||||
бесплатный
|
||||
{else}
|
||||
{$gift->getPrice()} голосов
|
||||
{/if}
|
||||
</strong>
|
||||
|
||||
<strong class="gift_limit">
|
||||
{if $gift->getUsagesLeft($thisUser) !== INF}
|
||||
осталось {$gift->getUsagesLeft($thisUser)} штук
|
||||
{/if}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 8px;">
|
||||
{include "../components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($gifts),
|
||||
"perPage" => OPENVK_DEFAULT_PER_PAGE,
|
||||
]}
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block bodyScripts}
|
||||
<script>
|
||||
$(".gift_sel").click(function() {
|
||||
let el = $(this);
|
||||
if(el.hasClass("disabled"))
|
||||
return false;
|
||||
|
||||
let link = "/gifts?act=confirm&user={$user->getId()}&pack={$cat->getId()}&elid=";
|
||||
let gift = el.data("gift");
|
||||
|
||||
window.location.assign(link + gift);
|
||||
});
|
||||
</script>
|
||||
{/block}
|
43
Web/Presenters/templates/Gifts/UserGifts.xml
Normal file
43
Web/Presenters/templates/Gifts/UserGifts.xml
Normal file
|
@ -0,0 +1,43 @@
|
|||
{extends "../@listView.xml"}
|
||||
|
||||
{block title}
|
||||
Подарки {$user->getFirstName()}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
Подарки
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
javascript:false
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->gift->getImage(2)}" width="75" alt="Подарок" />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
Подарок
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
<table class="ugc-table" n:if="$hideInfo ? !$x->anon : true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="nobold">Даритель: </span></td>
|
||||
<td>
|
||||
<a href="{$x->sender->getURL()}">
|
||||
{$x->sender->getFullName()}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr n:if="!empty($x->caption)">
|
||||
<td><span class="nobold">Комментарий: </span></td>
|
||||
<td>{$x->caption}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/block}
|
|
@ -84,6 +84,8 @@
|
|||
</a>
|
||||
{/if}
|
||||
|
||||
<a href="/gifts?act=pick&user={$user->getId()}" class="profile_link">Подарить подарок</a>
|
||||
|
||||
{var subStatus = $user->getSubscriptionStatus($thisUser)}
|
||||
{if $subStatus === 0}
|
||||
<form action="/setSub/user" method="post">
|
||||
|
@ -150,6 +152,30 @@
|
|||
{/if}
|
||||
</div>
|
||||
<br />
|
||||
<div n:if="($giftCount = $user->getGiftCount()) > 0">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$giftCount});">
|
||||
{_gifts}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("gifts", $giftCount)}
|
||||
<div style="float:right;">
|
||||
<a href="/gifts{$user->getId()}">{_all_title}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ovk-avView">
|
||||
<div class="ovk-avView--el" n:foreach="$user->getGifts(1, 3) as $giftDescriptor">
|
||||
{var hideInfo = $giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false}
|
||||
|
||||
<a href="{$hideInfo ? 'javascript:false' : $giftDescriptor->sender->getURL()}">
|
||||
<img class="ava"
|
||||
src="{$giftDescriptor->gift->getImage(2)}"
|
||||
alt="{$hideInfo ? 'Подарок' : ($giftDescriptor->caption ?? 'Подарок')}" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="$user->getFriendsCount() > 0 && $user->getPrivacyPermission('friends.read', $thisUser ?? NULL)">
|
||||
{var friendCount = $user->getFriendsCount()}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{var gift = $notification->getModel(0)}
|
||||
{var sender = $notification->getModel(1)}
|
||||
|
||||
<a href="{$sender->getURL()}"><b>{$sender->getCanonicalName()}</b></a> отправил вам {$notification->getDateTime()} подарок.
|
|
@ -17,6 +17,7 @@ services:
|
|||
- openvk\Web\Presenters\NotificationPresenter
|
||||
- openvk\Web\Presenters\SupportPresenter
|
||||
- openvk\Web\Presenters\AdminPresenter
|
||||
- openvk\Web\Presenters\GiftsPresenter
|
||||
- openvk\Web\Presenters\MessengerPresenter
|
||||
- openvk\Web\Presenters\ThemepacksPresenter
|
||||
- openvk\Web\Presenters\VKAPIPresenter
|
||||
|
@ -34,4 +35,5 @@ services:
|
|||
- openvk\Web\Models\Repositories\TicketComments
|
||||
- openvk\Web\Models\Repositories\IPs
|
||||
- openvk\Web\Models\Repositories\Vouchers
|
||||
- openvk\Web\Models\Repositories\Gifts
|
||||
- openvk\Web\Models\Repositories\ContentSearchRepository
|
||||
|
|
|
@ -199,6 +199,12 @@ routes:
|
|||
handler: "About->invite"
|
||||
- url: "/away.php"
|
||||
handler: "Away->away"
|
||||
- url: "/gift{num}_{num}.png"
|
||||
handler: "Gifts->giftImage"
|
||||
- url: "/gifts{num}"
|
||||
handler: "Gifts->userGifts"
|
||||
- url: "/gifts"
|
||||
handler: "Gifts->stub"
|
||||
- url: "/admin"
|
||||
handler: "Admin->index"
|
||||
- url: "/admin/users"
|
||||
|
@ -213,6 +219,14 @@ routes:
|
|||
handler: "Admin->vouchers"
|
||||
- url: "/admin/vouchers/id{num}"
|
||||
handler: "Admin->voucher"
|
||||
- url: "/admin/gifts"
|
||||
handler: "Admin->giftCategories"
|
||||
- url: "/admin/gifts/id{num}"
|
||||
handler: "Admin->gift"
|
||||
- url: "/admin/gifts/{slug}.{num}.meta"
|
||||
handler: "Admin->giftCategory"
|
||||
- url: "/admin/gifts/{slug}.{num}/"
|
||||
handler: "Admin->gifts"
|
||||
- url: "/admin/ban.pl/{num}"
|
||||
handler: "Admin->quickBan"
|
||||
- url: "/admin/warn.pl/{num}"
|
||||
|
|
|
@ -1306,4 +1306,43 @@ body.scrolled .toTop:hover {
|
|||
|
||||
.knowledgeBaseArticle ul {
|
||||
color: unset;
|
||||
}
|
||||
|
||||
.gift_grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.gift_sel {
|
||||
display: grid;
|
||||
box-sizing: border-box;
|
||||
padding: 15px 8px;
|
||||
justify-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.gift_pic {
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.gift_sel:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.gift_sel.disabled:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.gift_sel > .gift_price, .gift_sel > .gift_limit {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.gift_sel:hover > .gift_price, .gift_sel:hover > .gift_limit {
|
||||
visibility: unset;
|
||||
}
|
||||
|
||||
.gift_sel.disabled {
|
||||
opacity: .5;
|
||||
}
|
|
@ -62,8 +62,10 @@ function tr(string $stringId, ...$variables): string
|
|||
{
|
||||
$localizer = Localizator::i();
|
||||
$lang = Session::i()->get("lang", "ru");
|
||||
$output = $localizer->_($stringId, $lang);
|
||||
if($stringId === "__lang")
|
||||
return $lang;
|
||||
|
||||
$output = $localizer->_($stringId, $lang);
|
||||
if(sizeof($variables) > 0) {
|
||||
if(gettype($variables[0]) === "integer") {
|
||||
$numberedStringId = NULL;
|
||||
|
@ -108,17 +110,17 @@ function getLanguages(): array
|
|||
|
||||
function isLanguageAvailable($lg): bool
|
||||
{
|
||||
$lg_temp = false;
|
||||
foreach(getLanguages() as $lang) {
|
||||
if ($lang['code'] == $lg) $lg_temp = true;
|
||||
}
|
||||
return $lg_temp;
|
||||
$lg_temp = false;
|
||||
foreach(getLanguages() as $lang) {
|
||||
if ($lang['code'] == $lg) $lg_temp = true;
|
||||
}
|
||||
return $lg_temp;
|
||||
}
|
||||
|
||||
function getBrowsersLanguage(): array
|
||||
{
|
||||
if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] != null) return mb_split(",", mb_split(";", $_SERVER['HTTP_ACCEPT_LANGUAGE'])[0]);
|
||||
else return array();
|
||||
if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] != null) return mb_split(",", mb_split(";", $_SERVER['HTTP_ACCEPT_LANGUAGE'])[0]);
|
||||
else return array();
|
||||
}
|
||||
|
||||
function eventdb(): ?DatabaseConnection
|
||||
|
@ -196,12 +198,12 @@ return (function() {
|
|||
|
||||
setlocale(LC_TIME, "POSIX");
|
||||
|
||||
// TODO: Default language in config
|
||||
// TODO: Default language in config
|
||||
if(Session::i()->get("lang") == null) {
|
||||
$languages = array_reverse(getBrowsersLanguage());
|
||||
foreach($languages as $lg) {
|
||||
if(isLanguageAvailable($lg)) setLanguage($lg);
|
||||
}
|
||||
$languages = array_reverse(getBrowsersLanguage());
|
||||
foreach($languages as $lg) {
|
||||
if(isLanguageAvailable($lg)) setLanguage($lg);
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($_SERVER["REQUEST_SCHEME"]))
|
||||
|
@ -213,6 +215,7 @@ return (function() {
|
|||
else
|
||||
$ver = "Build 15";
|
||||
|
||||
define("nullptr", NULL);
|
||||
define("OPENVK_VERSION", "Altair Preview ($ver)", false);
|
||||
define("OPENVK_DEFAULT_PER_PAGE", 10, false);
|
||||
define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false);
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
"openvk\\Web\\Models\\Entities\\Ticket":16,
|
||||
"openvk\\Web\\Models\\Entities\\TicketComment":17,
|
||||
"openvk\\Web\\Models\\Entities\\User":18,
|
||||
"openvk\\Web\\Models\\Entities\\Video":19
|
||||
}
|
||||
"openvk\\Web\\Models\\Entities\\Video":19,
|
||||
"openvk\\Web\\Models\\Entities\\Gift":20
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue