feat(profile): fav games & custom fields (#1193)

* add fav games

* add additional fields

* add gui 4 dis

* add maxlength
This commit is contained in:
mrilyew 2024-12-20 17:34:29 +03:00 committed by GitHub
parent 4c9c650cff
commit 0aecc299cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 456 additions and 13 deletions

View file

@ -269,7 +269,7 @@ final class Account extends VKAPIRequestHandler
return 1;
$entity = get_entity_by_id($owner_id);
if(!$entity || $entity->isDeleted())
if(!$entity)
return 0;
if(!$entity->isBlacklistedBy($this->getUser()))
@ -296,4 +296,49 @@ final class Account extends VKAPIRequestHandler
return $result;
}
function saveInterestsInfo(
string $interests = NULL,
string $fav_music = NULL,
string $fav_films = NULL,
string $fav_shows = NULL,
string $fav_books = NULL,
string $fav_quote = NULL,
string $fav_games = NULL,
string $about = NULL,
)
{
$this->requireUser();
$this->willExecuteWriteAction();
$user = $this->getUser();
$changes = 0;
$changes_array = [
"interests" => $interests,
"fav_music" => $fav_music,
"fav_films" => $fav_films,
"fav_books" => $fav_books,
"fav_shows" => $fav_shows,
"fav_quote" => $fav_quote,
"fav_games" => $fav_games,
"about" => $about,
];
foreach($changes_array as $change_name => $change_value) {
$set_name = "set".ucfirst($change_name);
$get_name = "get".str_replace("Fav", "Favorite", str_replace("_", "", ucfirst($change_name)));
if(!is_null($change_value) && $change_value !== $user->$get_name()) {
$user->$set_name(ovk_proc_strtr($change_value, 1000));
$changes += 1;
}
}
if($changes > 0) {
$user->save();
}
return (object) [
"changed" => (int)($changes > 0),
];
}
}

View file

@ -12,8 +12,14 @@ final class Users extends VKAPIRequestHandler
if($authuser == NULL) $authuser = $this->getUser();
$users = new UsersRepo;
if($user_ids == "0")
if($user_ids == "0") {
if(!$authuser) {
return [];
}
$user_ids = (string) $authuser->getId();
}
$usrs = explode(',', $user_ids);
$response = array();
@ -198,6 +204,13 @@ final class Users extends VKAPIRequestHandler
$response[$i]->quotes = $usr->getFavoriteQuote();
break;
case "games":
if(!$canView) {
break;
}
$response[$i]->games = $usr->getFavoriteGames();
break;
case "email":
if(!$canView) {
break;
@ -280,6 +293,17 @@ final class Users extends VKAPIRequestHandler
$response[$i]->blacklisted = (int)$this->getUser()->isBlacklistedBy($usr);
break;
case "custom_fields":
if(sizeof($usrs) > 1)
break;
$c_fields = \openvk\Web\Models\Entities\UserInfoEntities\AdditionalField::getByOwner($usr->getId());
$append_array = [];
foreach($c_fields as $c_field)
$append_array[] = $c_field->toVkApiStruct();
$response[$i]->custom_fields = $append_array;
break;
}
}

View file

@ -228,6 +228,11 @@ class User extends RowModel
return $this->getRecord()->about;
}
function getAbout(): ?string
{
return $this->getRecord()->about;
}
function getStatus(): ?string
{
return $this->getRecord()->status;
@ -415,6 +420,11 @@ class User extends RowModel
return $this->getRecord()->fav_quote;
}
function getFavoriteGames(): ?string
{
return $this->getRecord()->fav_games;
}
function getCity(): ?string
{
return $this->getRecord()->city;
@ -425,6 +435,30 @@ class User extends RowModel
return $this->getRecord()->address;
}
function getAdditionalFields(bool $split = false): array
{
$all = \openvk\Web\Models\Entities\UserInfoEntities\AdditionalField::getByOwner($this->getId());
$result = [
"interests" => [],
"contacts" => [],
];
if($split) {
foreach($all as $field) {
if($field->getPlace() == "contact")
$result["contacts"][] = $field;
else if($field->getPlace() == "interest")
$result["interests"][] = $field;
}
} else {
$result = [];
foreach($all as $field)
$result[] = $field;
}
return $result;
}
function getNotificationOffset(): int
{
return $this->getRecord()->notification_offset;
@ -1432,6 +1466,9 @@ class User extends RowModel
$res->blacklisted = (int)$user->isBlacklistedBy($this);
break;
case "games":
$res->games = $this->getFavoriteGames();
break;
}
}

View file

@ -0,0 +1,95 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\UserInfoEntities;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Repositories\Users;
use Chandler\Database\DatabaseConnection;
class AdditionalField extends RowModel
{
protected $tableName = "additional_fields";
const PLACE_CONTACTS = 0;
const PLACE_INTERESTS = 1;
function getOwner(): int
{
return (int) $this->getRecord()->owner;
}
function getName(bool $tr = true): string
{
$orig_name = $this->getRecord()->name;
$name = $orig_name;
if($tr && $name[0] === "_")
$name = tr("custom_field_" . substr($name, 1));
if(str_contains($name, "custom_field"))
return $orig_name;
return $name;
}
function getContent(): string
{
return $this->getRecord()->text;
}
function getPlace(): string
{
switch($this->getRecord()->place) {
case AdditionalField::PLACE_CONTACTS:
return "contact";
case AdditionalField::PLACE_INTERESTS:
return "interest";
}
return "contact";
}
function isContact(): bool
{
return $this->getRecord()->place == AdditionalField::PLACE_CONTACTS;
}
function toVkApiStruct(): object
{
return (object) [
"type" => $this->getRecord()->place,
"name" => $this->getName(),
"text" => $this->getContent()
];
}
static function getById(int $id)
{
$ctx = DatabaseConnection::i()->getContext();
$entry = $ctx->table("additional_fields")->where("id", $id)->fetch();
if(!$entry)
return NULL;
return new AdditionalField($entry);
}
static function getByOwner(int $owner): \Traversable
{
$ctx = DatabaseConnection::i()->getContext();
$entries = $ctx->table("additional_fields")->where("owner", $owner);
foreach($entries as $entry) {
yield new AdditionalField($entry);
}
}
static function getCountByOwner(int $owner): \Traversable
{
return DatabaseConnection::i()->getContext()->table("additional_fields")->where("owner", $owner)->count();
}
static function resetByOwner(int $owner): bool
{
DatabaseConnection::i()->getContext()->table("additional_fields")->where("owner", $owner)->delete();
return true;
}
}

View file

@ -64,6 +64,7 @@ final class UserPresenter extends OpenVKPresenter
$this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($user->getId());
$this->template->audiosCount = (new Audios)->getUserCollectionSize($user);
$this->template->audioStatus = $user->getCurrentAudioStatus();
$this->template->additionalFields = $user->getAdditionalFields(true);
$this->template->user = $user;
@ -251,13 +252,14 @@ final class UserPresenter extends OpenVKPresenter
else
$user->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website);
} elseif($_GET['act'] === "interests") {
$user->setInterests(empty($this->postParam("interests")) ? NULL : ovk_proc_strtr($this->postParam("interests"), 300));
$user->setFav_Music(empty($this->postParam("fav_music")) ? NULL : ovk_proc_strtr($this->postParam("fav_music"), 300));
$user->setFav_Films(empty($this->postParam("fav_films")) ? NULL : ovk_proc_strtr($this->postParam("fav_films"), 300));
$user->setFav_Shows(empty($this->postParam("fav_shows")) ? NULL : ovk_proc_strtr($this->postParam("fav_shows"), 300));
$user->setFav_Books(empty($this->postParam("fav_books")) ? NULL : ovk_proc_strtr($this->postParam("fav_books"), 300));
$user->setFav_Quote(empty($this->postParam("fav_quote")) ? NULL : ovk_proc_strtr($this->postParam("fav_quote"), 300));
$user->setAbout(empty($this->postParam("about")) ? NULL : ovk_proc_strtr($this->postParam("about"), 300));
$user->setInterests(empty($this->postParam("interests")) ? NULL : ovk_proc_strtr($this->postParam("interests"), 1000));
$user->setFav_Music(empty($this->postParam("fav_music")) ? NULL : ovk_proc_strtr($this->postParam("fav_music"), 1000));
$user->setFav_Films(empty($this->postParam("fav_films")) ? NULL : ovk_proc_strtr($this->postParam("fav_films"), 1000));
$user->setFav_Shows(empty($this->postParam("fav_shows")) ? NULL : ovk_proc_strtr($this->postParam("fav_shows"), 1000));
$user->setFav_Books(empty($this->postParam("fav_books")) ? NULL : ovk_proc_strtr($this->postParam("fav_books"), 1000));
$user->setFav_Quote(empty($this->postParam("fav_quote")) ? NULL : ovk_proc_strtr($this->postParam("fav_quote"), 1000));
$user->setFav_Games(empty($this->postParam("fav_games")) ? NULL : ovk_proc_strtr($this->postParam("fav_games"), 1000));
$user->setAbout(empty($this->postParam("about")) ? NULL : ovk_proc_strtr($this->postParam("about"), 1000));
} elseif($_GET["act"] === "backdrop") {
if($this->postParam("subact") === "remove") {
$user->unsetBackDropPictures();
@ -295,10 +297,46 @@ final class UserPresenter extends OpenVKPresenter
$this->returnJson([
"success" => true
]);
} elseif($_GET['act'] === "additional") {
$maxAddFields = ovkGetQuirk("users.max-fields");
$items = [];
for($i = 0; $i < $maxAddFields; $i++) {
if(!$this->postParam("name_".$i)) {
continue;
}
$items[] = [
"name" => $this->postParam("name_".$i),
"text" => $this->postParam("text_".$i),
"place" => $this->postParam("place_".$i),
];
}
\openvk\Web\Models\Entities\UserInfoEntities\AdditionalField::resetByOwner($this->user->id);
foreach($items as $new_field_info) {
$name = ovk_proc_strtr($new_field_info["name"], 50);
$text = ovk_proc_strtr($new_field_info["text"], 1000);
if(ctype_space($name) || ctype_space($text)) {
continue;
}
$place = (int)($new_field_info["place"]);
$new_field = new \openvk\Web\Models\Entities\UserInfoEntities\AdditionalField;
$new_field->setOwner($this->user->id);
$new_field->setName($name);
$new_field->setText($text);
$new_field->setPlace([0, 1][$place] ? $place : 0);
$new_field->save();
}
}
try {
if($_GET['act'] !== "additional") {
$user->save();
}
} catch(\PDOException $ex) {
if($ex->getCode() == 23000)
$this->flashFail("err", tr("error"), tr("error_shorturl"));
@ -310,7 +348,7 @@ final class UserPresenter extends OpenVKPresenter
}
$this->template->mode = in_array($this->queryParam("act"), [
"main", "contacts", "interests", "avatar", "backdrop"
"main", "contacts", "interests", "avatar", "backdrop", "additional"
]) ? $this->queryParam("act")
: "main";

View file

@ -460,6 +460,7 @@
"max_filesize_mb": 5,
"current_id": {$thisUser ? $thisUser->getId() : 0},
"disable_ajax": {$disable_ajax ? $disable_ajax : 0},
"max_add_fields": {ovkGetQuirk("users.max-fields")},
}
</script>

View file

@ -13,6 +13,7 @@
{var $isMain = $mode === 'main'}
{var $isContacts = $mode === 'contacts'}
{var $isInterests = $mode === 'interests'}
{var $isAdditional = $mode === 'additional'}
{var $isAvatar = $mode === 'avatar'}
{var $isBackDrop = $mode === 'backdrop'}
@ -31,6 +32,9 @@
<div n:attr="id => ($isInterests ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isInterests ? 'act_tab_a' : 'ki')" href="/edit?act=interests">{_interests}</a>
</div>
<div n:attr="id => ($isAdditional ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isAdditional ? 'act_tab_a' : 'ki')" href="/edit?act=additional">{_additional}</a>
</div>
<div n:attr="id => ($isAvatar ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isAvatar ? 'act_tab_a' : 'ki')" href="/edit?act=avatar">{_avatar}</a>
</div>
@ -294,6 +298,14 @@
<textarea type="text" name="fav_quote">{$user->getFavoriteQuote()}</textarea>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_favorite_games}: </span>
</td>
<td>
<textarea type="text" name="fav_games">{$user->getFavoriteGames()}</textarea>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_information_about}: </span>
@ -375,7 +387,58 @@
</center>
</div>
</form>
{elseif $isAdditional}
{var $f_iterator = 0}
<h4>{_additional_information}</h4>
<p>{tr("additional_fields_description", ovkGetQuirk("users.max-fields"))}</p>
<form id="additional_fields_form" method="POST" enctype="multipart/form-data">
<div class="edit_field_container_inserts">
<table data-iterator="{$f_iterator}" class="outline_table edit_field_container_item" width="80%" border="0" align="center" n:foreach="$thisUser->getAdditionalFields() as $field">
<tbody>
<tr>
<td width="150">
{_additional_field_name}
</td>
<td>
<input name="name_{$f_iterator}" maxlength="50" type="text" value="{$field->getName(false)}">
</td>
<td>
<div id="small_remove_button"></div>
</td>
</tr>
<tr>
<td valign="top">
{_additional_field_text}
</td>
<td>
<textarea name="text_{$f_iterator}" maxlength="1000">{$field->getContent()}</textarea>
</td>
<td></td>
</tr>
<tr>
<td>
{_additional_field_place}
</td>
<td>
<select name="place_{$f_iterator}">
<option value="0" n:attr="selected => $field->isContact()">{_additional_field_place_contacts}</option>
<option value="1" n:attr="selected => !$field->isContact()">{_additional_field_place_interests}</option>
</select>
</td>
<td></td>
</tr>
</tbody>
{php $f_iterator += 1}
</table>
</div>
<div class="flex_column_center_gap5px">
<input type="button" id="additional_field_append" value="{_add}" class="button" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_save}" class="button" />
</div>
</form>
{/if}
</div>

View file

@ -506,6 +506,12 @@
<td class="label"><span class="nobold">{_address}:</span></td>
<td class="data">{$user->getPhysicalAddress()}</td>
</tr>
{if $additionalFields}
<tr n:foreach="$additionalFields['contacts'] as $field">
<td class="label"><span class="nobold">{$field->getName()}:</span></td>
<td class="data">{$field->getContent()}</td>
</tr>
{/if}
</tbody>
</table>
{/capture}
@ -567,6 +573,16 @@
<td class="label"><span class="nobold">{_favorite_quotes}: </span></td>
<td class="data">{$user->getFavoriteQuote()}</td>
</tr>
<tr n:if="!is_null($user->getFavoriteGames())">
<td class="label"><span class="nobold">{_favorite_games}: </span></td>
<td class="data">{$user->getFavoriteGames()}</td>
</tr>
{if $additionalFields}
<tr n:foreach="$additionalFields['interests'] as $field">
<td class="label"><span class="nobold">{$field->getName()}:</span></td>
<td class="data">{$field->getContent()}</td>
</tr>
{/if}
<tr n:if="!is_null($user->getDescription())">
<td class="label"><span class="nobold">{_information_about}: </span></td>
<td class="data">{$user->getDescription()}</td>

View file

@ -506,6 +506,28 @@ table {
text-align: left;
}
.outline_table td {
text-align: right;
font-weight: normal;
color: gray;
}
.outline_table {
border-bottom: 1px solid #cbcbcb;
padding: 5px 0px;
}
.outline_table:last-of-type {
border-bottom: unset;
}
.flex_column_center_gap5px {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.information {
padding: 9px;
background-color: #c3e4ff;

View file

@ -924,7 +924,8 @@ u(document).on("click", "#editPost", async (e) => {
const target = u(e.target)
const post = target.closest("table")
const content = post.find(".post-content")
const edit_place = post.find('.post-edit')
const edit_place_l = post.find('.post-edit')
const edit_place = u(edit_place_l.first())
const id = post.attr('data-id').split('_')
let type = 'post'
@ -2886,3 +2887,61 @@ u(document).on('click', '#_bl_toggler', async (e) => {
}
}
})
/* Additional fields */
u(document).on("click", "#additional_field_append", (e) => {
let iterator = 0
if(u(`table[data-iterator]`).last()) {
iterator = Number(u(`table[data-iterator]`).last().dataset.iterator) + 1
}
if(iterator >= window.openvk.max_add_fields) {
return
}
u('.edit_field_container_inserts').append(`
<table data-iterator="${iterator}" class="outline_table edit_field_container_item" width="80%" border="0" align="center">
<tbody>
<tr>
<td width="150">${tr("additional_field_name")}</td>
<td><input name="name_${iterator}" type="text" maxlength="50"></td>
<td><div id="small_remove_button"></div></td>
</tr>
<tr>
<td valign="top">${tr("additional_field_text")}</td>
<td><textarea name="text_${iterator}" maxlength="1000"></textarea></td><td></td>
</tr>
<tr>
<td>${tr("additional_field_place")}</td>
<td>
<select name="place_${iterator}">
<option value="0">${tr("additional_field_place_contacts")}</option>
<option value="1" selected>${tr("additional_field_place_interests")}</option>
</select>
</td><td></td>
</tr>
</tbody>
</table>
`)
u(`.edit_field_container_item[data-iterator='${iterator}'] input[type="text"]`).nodes[0].focus()
})
u(document).on("click", ".edit_field_container_item #small_remove_button", (e) => {
let iterator = 0
u(e.target).closest('table').remove()
u(".edit_field_container_inserts .edit_field_container_item").nodes.forEach(node => {
node.setAttribute('data-iterator', iterator)
iterator += 1
})
})
u(document).on("submit", "#additional_fields_form", (e) => {
u(`.edit_field_container_item input, .edit_field_container_item textarea`).nodes.forEach(node => {
if(node.value == "" || node.value == " ") {
e.preventDefault()
node.focus()
return
}
})
})

View file

@ -0,0 +1,10 @@
ALTER TABLE `profiles` ADD `fav_games` MEDIUMTEXT NULL DEFAULT NULL AFTER `fav_quote`;
CREATE TABLE `additional_fields` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`owner` BIGINT(20) UNSIGNED NOT NULL,
`name` VARCHAR(255) COLLATE utf8mb4_unicode_520_ci NOT NULL,
`text` MEDIUMTEXT COLLATE utf8mb4_unicode_520_ci NOT NULL,
`place` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
ALTER TABLE `additional_fields` ADD INDEX(`owner`);

View file

@ -147,11 +147,27 @@
"personal_information" = "Personal information";
"interests" = "Interests";
"additional" = "Additional";
"additional_fields_description" = "There you can add additional info about you, like another profiles links or your interests. Up to a maximum of $1 such fields can be added.";
"additional_field_name" = "Name";
"additional_field_text" = "Text";
"additional_field_place" = "Place";
"additional_field_place_contacts" = "In \"contacts\"";
"additional_field_place_interests" = "In \"interests\"";
"favorite_music" = "Favorite music";
"favorite_films" = "Favorite flims";
"favorite_shows" = "Favorite TV-shows";
"favorite_books" = "Favorite books";
"favorite_quotes" = "Favorite quotes";
"favorite_games" = "Favorite games";
"custom_field_favorite_performers" = "Favourite performers";
"custom_field_favorite_content_makers" = "Favourite content-makers";
"custom_field_favorite_anime" = "Favourite anime";
"custom_field_favorite_manga" = "Favourite manga";
"custom_field_favorite_vtubers" = "Favourite vtubers";
"custom_field_favorite_albums" = "Favourite music albums";
"information_about" = "About";
"updated_at" = "Updated at $1";

View file

@ -131,11 +131,27 @@
"address" = "Адрес";
"personal_information" = "Личная информация";
"interests" = "Интересы";
"additional" = "Дополнительно";
"additional_fields_description" = "Здесь вы можете добавить дополнительную информацию о себе: ссылки на ваши социальные сети, либо же ваши интересы. Максимум можно добавить до $1 таких полей.";
"additional_field_name" = "Название";
"additional_field_text" = "Текст";
"additional_field_place" = "Отображение";
"additional_field_place_contacts" = "В \"контактах\"";
"additional_field_place_interests" = "В \"интересах\"";
"favorite_music" = "Любимая музыка";
"favorite_films" = "Любимые фильмы";
"favorite_shows" = "Любимые ТВ-шоу";
"favorite_books" = "Любимые книги";
"favorite_quotes" = "Любимые цитаты";
"favorite_games" = "Любимые игры";
"custom_field_favorite_performers" = "Любимые исполнители";
"custom_field_favorite_content_makers" = "Любимые контент-мейкеры";
"custom_field_favorite_anime" = "Любимые аниме";
"custom_field_favorite_manga" = "Любимая манга";
"custom_field_favorite_vtubers" = "Любимые витуберы";
"custom_field_favorite_albums" = "Любимые альбомы";
"information_about" = "О себе";
"updated_at" = "Обновлено $1";
"user_banned" = "К сожалению, нам пришлось заблокировать страницу пользователя <b>$1</b>.";

View file

@ -46,3 +46,4 @@ comments.allow-graffiti: 0
wall.repost-liking-recursion-limit: 10
polls.max-opts: 10
users.max-fields: 7