From 0aecc299cf42c692bb4963d5741832570fc02efb Mon Sep 17 00:00:00 2001 From: mrilyew <99399973+mrilyew@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:34:29 +0300 Subject: [PATCH] feat(profile): fav games & custom fields (#1193) * add fav games * add additional fields * add gui 4 dis * add maxlength --- VKAPI/Handlers/Account.php | 47 ++++++++- VKAPI/Handlers/Users.php | 26 ++++- Web/Models/Entities/User.php | 37 ++++++++ .../UserInfoEntities/AdditionalField.php | 95 +++++++++++++++++++ Web/Presenters/UserPresenter.php | 56 +++++++++-- Web/Presenters/templates/@layout.xml | 1 + Web/Presenters/templates/User/Edit.xml | 63 ++++++++++++ Web/Presenters/templates/User/View.xml | 16 ++++ Web/static/css/main.css | 22 +++++ Web/static/js/al_wall.js | 61 +++++++++++- install/sqls/00053-new-fields.sql | 10 ++ locales/en.strings | 16 ++++ locales/ru.strings | 16 ++++ quirks.yml | 3 +- 14 files changed, 456 insertions(+), 13 deletions(-) create mode 100644 Web/Models/Entities/UserInfoEntities/AdditionalField.php create mode 100644 install/sqls/00053-new-fields.sql diff --git a/VKAPI/Handlers/Account.php b/VKAPI/Handlers/Account.php index c1f3ef23..d4144b90 100644 --- a/VKAPI/Handlers/Account.php +++ b/VKAPI/Handlers/Account.php @@ -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), + ]; + } } diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index 7dc68f3b..c47acb8e 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -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; } } diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 61ea120b..a1f650ec 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -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; } } diff --git a/Web/Models/Entities/UserInfoEntities/AdditionalField.php b/Web/Models/Entities/UserInfoEntities/AdditionalField.php new file mode 100644 index 00000000..47c69de3 --- /dev/null +++ b/Web/Models/Entities/UserInfoEntities/AdditionalField.php @@ -0,0 +1,95 @@ +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; + } +} diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index f238e381..dc8fb02c 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -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 { - $user->save(); + 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"; diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 34e033dc..245ccfb0 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -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")}, } diff --git a/Web/Presenters/templates/User/Edit.xml b/Web/Presenters/templates/User/Edit.xml index 8bea7f0c..0cc70a63 100644 --- a/Web/Presenters/templates/User/Edit.xml +++ b/Web/Presenters/templates/User/Edit.xml @@ -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 @@
{_interests}
+
+ {_additional} +
{_avatar}
@@ -294,6 +298,14 @@ + + + {_favorite_games}: + + + + + {_information_about}: @@ -375,7 +387,58 @@ + {elseif $isAdditional} + {var $f_iterator = 0} +

{_additional_information}

+

{tr("additional_fields_description", ovkGetQuirk("users.max-fields"))}

+
+
+ + + + + + + + + + + + + + + + + + + {php $f_iterator += 1} +
+ {_additional_field_name} + + + +
+
+ {_additional_field_text} + + +
+ {_additional_field_place} + + +
+
+ +
+ + + +
+
{/if} diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml index cbcb1f1f..9c013dd1 100644 --- a/Web/Presenters/templates/User/View.xml +++ b/Web/Presenters/templates/User/View.xml @@ -506,6 +506,12 @@ {_address}: {$user->getPhysicalAddress()} + {if $additionalFields} + + {$field->getName()}: + {$field->getContent()} + + {/if} {/capture} @@ -567,6 +573,16 @@ {_favorite_quotes}: {$user->getFavoriteQuote()} + + {_favorite_games}: + {$user->getFavoriteGames()} + + {if $additionalFields} + + {$field->getName()}: + {$field->getContent()} + + {/if} {_information_about}: {$user->getDescription()} diff --git a/Web/static/css/main.css b/Web/static/css/main.css index 7333b22b..94f5939d 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -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; diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index a20b0d9d..e6229b28 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -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(` + + + + + + + + + + + + + + + + +
${tr("additional_field_name")}
${tr("additional_field_text")}
${tr("additional_field_place")} + +
+ `) + 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 + } + }) +}) diff --git a/install/sqls/00053-new-fields.sql b/install/sqls/00053-new-fields.sql new file mode 100644 index 00000000..0b437e57 --- /dev/null +++ b/install/sqls/00053-new-fields.sql @@ -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`); diff --git a/locales/en.strings b/locales/en.strings index 2cfda2eb..1aff6d88 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -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"; diff --git a/locales/ru.strings b/locales/ru.strings index 2a79df8a..93439550 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -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" = "К сожалению, нам пришлось заблокировать страницу пользователя $1."; diff --git a/quirks.yml b/quirks.yml index 8a1b7878..8fe09b2f 100644 --- a/quirks.yml +++ b/quirks.yml @@ -45,4 +45,5 @@ comments.allow-graffiti: 0 # + Set this option to any non-negative number to be this limit wall.repost-liking-recursion-limit: 10 -polls.max-opts: 10 \ No newline at end of file +polls.max-opts: 10 +users.max-fields: 7