diff --git a/README.md b/README.md index 4bc98cdcc..c068aad43 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Once you are done, you can login as a system administrator on the network itself * **Password**: `admin` * It is highly recommended to change the password of the built-in account or disable it. -💡Confused? Full installation walkthrough is available [here](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)). +💡Confused? Full installation walkthrough is available [here](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)). ### Looking for Docker or Kubernetes deployment? See `install/automated/docker/README.md` and `install/automated/kubernetes/README.md` for Docker and Kubernetes deployment instructions. diff --git a/README_RU.md b/README_RU.md index de7f77faf..9c572d331 100644 --- a/README_RU.md +++ b/README_RU.md @@ -76,7 +76,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions * **Пароль**: `admin` * Перед использованием встроенной учетной записи настоятельно рекомендуется сменить пароль или отключить её. -💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)). +💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)). # Установка в Docker/Kubernetes Подробные иструкции можно найти в `install/automated/docker/README.md` и `install/automated/kubernetes/README.md` соответственно. diff --git a/VKAPI/Handlers/Board.php b/VKAPI/Handlers/Board.php new file mode 100644 index 000000000..3a4412758 --- /dev/null +++ b/VKAPI/Handlers/Board.php @@ -0,0 +1,429 @@ +requireUser(); + $this->willExecuteWriteAction(); + + $club = (new ClubsRepo)->get($group_id); + + if(!$club) { + $this->fail(403, "Invalid club"); + } + + if(!$club->canBeModifiedBy($this->getUser()) && !$club->isEveryoneCanCreateTopics()) { + $this->fail(403, "Access to club denied"); + } + + $flags = 0; + + if($from_group == true && $club->canBeModifiedBy($this->getUser())) + $flags |= 0b10000000; + + $topic = new Topic; + $topic->setGroup($club->getId()); + $topic->setOwner($this->getUser()->getId()); + $topic->setTitle(ovk_proc_strtr($title, 127)); + $topic->setCreated(time()); + $topic->setFlags($flags); + $topic->save(); + + if(!empty($text)) { + $comment = new Comment; + $comment->setOwner($this->getUser()->getId()); + $comment->setModel(get_class($topic)); + $comment->setTarget($topic->getId()); + $comment->setContent($text); + $comment->setCreated(time()); + $comment->setFlags($flags); + $comment->save(); + + if(!empty($attachments)) { + $attachmentsArr = explode(",", $attachments); + # блин а мне это везде копировать типа + + if(sizeof($attachmentsArr) > 10) + $this->fail(50, "Error: too many attachments"); + + foreach($attachmentsArr as $attac) { + $attachmentType = NULL; + + if(str_contains($attac, "photo")) + $attachmentType = "photo"; + elseif(str_contains($attac, "video")) + $attachmentType = "video"; + else + $this->fail(205, "Unknown attachment type"); + + $attachment = str_replace($attachmentType, "", $attac); + + $attachmentOwner = (int)explode("_", $attachment)[0]; + $attachmentId = (int)end(explode("_", $attachment)); + + $attacc = NULL; + + if($attachmentType == "photo") { + $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Photo does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this photo"); + + $comment->attach($attacc); + } elseif($attachmentType == "video") { + $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Video does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this video"); + + $comment->attach($attacc); + } + + } + + } + + } + + return $topic->getId(); + } + + function closeTopic(int $group_id, int $topic_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + return 0; + } + + if(!$topic->isClosed()) { + $topic->setClosed(1); + $topic->save(); + } + + return 1; + } + + function createComment(int $group_id, int $topic_id, string $message = "", string $attachments = "", bool $from_group = true) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if(empty($message) && empty($attachments)) { + $this->fail(100, "Required parameter 'message' missing."); + } + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || $topic->isDeleted() || $topic->isClosed()) { + $this->fail(100, "Topic is deleted, closed or invalid."); + } + + $flags = 0; + + if($from_group != 0 && !is_null($topic->getClub()) && $topic->getClub()->canBeModifiedBy($this->user)) + $flags |= 0b10000000; + + if(strlen($message) > 300) { + $this->fail(20, "Comment is too long."); + } + + $comment = new Comment; + $comment->setOwner($this->getUser()->getId()); + $comment->setModel(get_class($topic)); + $comment->setTarget($topic->getId()); + $comment->setContent($message); + $comment->setCreated(time()); + $comment->setFlags($flags); + $comment->save(); + + if(!empty($attachments)) { + $attachmentsArr = explode(",", $attachments); + + if(sizeof($attachmentsArr) > 10) + $this->fail(50, "Error: too many attachments"); + + foreach($attachmentsArr as $attac) { + $attachmentType = NULL; + + if(str_contains($attac, "photo")) + $attachmentType = "photo"; + elseif(str_contains($attac, "video")) + $attachmentType = "video"; + else + $this->fail(205, "Unknown attachment type"); + + $attachment = str_replace($attachmentType, "", $attac); + + $attachmentOwner = (int)explode("_", $attachment)[0]; + $attachmentId = (int)end(explode("_", $attachment)); + + $attacc = NULL; + + if($attachmentType == "photo") { + $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Photo does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this photo"); + + $comment->attach($attacc); + } elseif($attachmentType == "video") { + $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Video does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this video"); + + $comment->attach($attacc); + } + } + } + + return $comment->getId(); + } + + function deleteComment(int $comment_id, int $group_id = 0, int $topic_id = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $comment = (new CommentsRepo)->get($comment_id); + + if($comment->isDeleted() || !$comment || !$comment->canBeDeletedBy($this->getUser())) + $this->fail(403, "Access to comment denied"); + + $comment->delete(); + + return 1; + } + + function deleteTopic(int $group_id, int $topic_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + return 0; + } + + $topic->deleteTopic(); + + return 1; + } + + function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments) + { + /* + $this->requireUser(); + $this->willExecuteWriteAction(); + + $comment = (new CommentsRepo)->get($comment_id); + + if($comment->getOwner() != $this->getUser()->getId()) + $this->fail(15, "Access to comment denied"); + + $comment->setContent($message); + $comment->setEdited(time()); + $comment->save(); + */ + return 1; + } + + function editTopic(int $group_id, int $topic_id, string $title) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + return 0; + } + + $topic->setTitle(ovk_proc_strtr($title, 127)); + + $topic->save(); + + return 1; + } + + function fixTopic(int $group_id, int $topic_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + return 0; + } + + $topic->setPinned(1); + + $topic->save(); + + return 1; + } + + function getComments(int $group_id, int $topic_id, bool $need_likes = false, int $start_comment_id = 0, int $offset = 0, int $count = 40, bool $extended = false, string $sort = "asc") + { + # start_comment_id ne robit + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + $this->fail(5, "Invalid topic"); + } + + $arr = [ + "items" => [] + ]; + + $comms = array_slice(iterator_to_array($topic->getComments(1, $count + $offset)), $offset); + foreach($comms as $comm) { + $arr["items"][] = $this->getApiBoardComment($comm, $need_likes); + + if($extended) { + if($comm->getOwner() instanceof \openvk\Web\Models\Entities\User) { + $arr["profiles"][] = $comm->getOwner()->toVkApiStruct(); + } + + if($comm->getOwner() instanceof \openvk\Web\Models\Entities\Club) { + $arr["groups"][] = $comm->getOwner()->toVkApiStruct(); + } + } + } + + return $arr; + } + + function getTopics(int $group_id, string $topic_ids = "", int $order = 1, int $offset = 0, int $count = 40, bool $extended = false, int $preview = 0, int $preview_length = 90) + { + # order и extended ничё не делают + $this->requireUser(); + $this->willExecuteWriteAction(); + + $arr = []; + $club = (new ClubsRepo)->get($group_id); + $topics = array_slice(iterator_to_array((new TopicsRepo)->getClubTopics($club, 1, $count + $offset)), $offset); + $arr["count"] = (new TopicsRepo)->getClubTopicsCount($club); + $arr["items"] = []; + $arr["default_order"] = $order; + $arr["can_add_topics"] = $club->canBeModifiedBy($this->getUser()) ? true : $club->isEveryoneCanCreateTopics() ? true : false; + $arr["profiles"] = []; + + if(empty($topic_ids)) { + foreach($topics as $topic) { + if($topic->isDeleted()) continue; + $arr["items"][] = $topic->toVkApiStruct($preview, $preview_length); + } + } else { + $topics = explode(',', $topic_ids); + + foreach($topics as $topic) { + $id = explode("_", $topic); + $topicy = (new TopicsRepo)->getTopicById((int)$id[0], (int)$id[1]); + if($topicy) { + $arr["items"] = $topicy->toVkApiStruct(); + } + } + } + + return $arr; + } + + function openTopic(int $group_id, int $topic_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || !$topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + return 0; + } + + if($topic->isClosed()) { + $topic->setClosed(0); + $topic->save(); + } + + return 1; + } + + function restoreComment(int $group_id, int $topic_id, int $comment_id) + { + $this->fail(501, "Not implemented"); + } + + function unfixTopic(int $group_id, int $topic_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); + + if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + return 0; + } + + if($topic->isPinned()) { + $topic->setClosed(0); + $topic->save(); + } + + $topic->setPinned(0); + + $topic->save(); + + return 1; + } + + private function getApiBoardComment(?Comment $comment, bool $need_likes = false) + { + $res = (object) []; + + $res->id = $comment->getId(); + $res->from_id = $comment->getOwner()->getId(); + $res->date = $comment->getPublicationTime()->timestamp(); + $res->text = $comment->getText(); + $res->attachments = []; + $res->likes = []; + if($need_likes) { + $res->likes = [ + "count" => $comment->getLikesCount(), + "user_likes" => (int) $comment->hasLikeFrom($this->getUser()), + "can_like" => 1 # а чё типо не может ахахаххахах + ]; + } + + foreach($comment->getChildren() as $attachment) { + if($attachment->isDeleted()) + continue; + + $res->attachments[] = $attachment->toVkApiStruct(); + } + + return $res; + } +} \ No newline at end of file diff --git a/VKAPI/Handlers/Gifts.php b/VKAPI/Handlers/Gifts.php new file mode 100644 index 000000000..2702924d5 --- /dev/null +++ b/VKAPI/Handlers/Gifts.php @@ -0,0 +1,174 @@ +requireUser(); + + $i = 0; + + $i += $offset; + + $user = (new UsersRepo)->get($user_id); + + if(!$user || $user->isDeleted()) + $this->fail(177, "Invalid user"); + + $gift_item = []; + + $userGifts = array_slice(iterator_to_array($user->getGifts(1, $count, false)), $offset); + + if(sizeof($userGifts) < 0) { + return NULL; + } + + foreach($userGifts as $gift) { + if($i < $count) { + $gift_item[] = [ + "id" => $i, + "from_id" => $gift->anon == true ? 0 : $gift->sender->getId(), + "message" => $gift->caption == NULL ? "" : $gift->caption, + "date" => $gift->sent->timestamp(), + "gift" => [ + "id" => $gift->gift->getId(), + "thumb_256" => $gift->gift->getImage(2), + "thumb_96" => $gift->gift->getImage(2), + "thumb_48" => $gift->gift->getImage(2) + ], + "privacy" => 0 + ]; + } + $i+=1; + } + + return $gift_item; + } + + function send(int $user_ids, int $gift_id, string $message = "", int $privacy = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $user = (new UsersRepo)->get((int) $user_ids); + + if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce']) + $this->fail(105, "Commerce is disabled on this instance"); + + if(!$user || $user->isDeleted()) + $this->fail(177, "Invalid user"); + + $gift = (new GiftsRepo)->get($gift_id); + + if(!$gift) + $this->fail(165, "Invalid gift"); + + $price = $gift->getPrice(); + $coinsLeft = $this->getUser()->getCoins() - $price; + + if(!$gift->canUse($this->getUser())) + return (object) + [ + "success" => 0, + "user_ids" => $user_ids, + "error" => "You don't have any more of these gifts." + ]; + + if($coinsLeft < 0) + return (object) + [ + "success" => 0, + "user_ids" => $user_ids, + "error" => "You don't have enough voices." + ]; + + $user->gift($this->getUser(), $gift, $message); + $gift->used(); + + $this->getUser()->setCoins($coinsLeft); + $this->getUser()->save(); + + $notification = new GiftNotification($user, $this->getUser(), $gift, $message); + $notification->emit(); + + return (object) + [ + "success" => 1, + "user_ids" => $user_ids, + "withdraw_votes" => $price + ]; + } + + function delete() + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $this->fail(501, "Not implemented"); + } + + # этих методов не было в ВК, но я их добавил чтобы можно было отобразить список подарков + function getCategories(bool $extended = false, int $page = 1) + { + $cats = (new GiftsRepo)->getCategories($page); + $categ = []; + $i = 0; + + if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce']) + $this->fail(105, "Commerce is disabled on this instance"); + + foreach($cats as $cat) { + $categ[$i] = [ + "name" => $cat->getName(), + "description" => $cat->getDescription(), + "id" => $cat->getId(), + "thumbnail" => $cat->getThumbnailURL(), + ]; + + if($extended == true) { + $categ[$i]["localizations"] = []; + foreach(getLanguages() as $lang) { + $code = $lang["code"]; + $categ[$i]["localizations"][$code] = + [ + "name" => $cat->getName($code), + "desc" => $cat->getDescription($code), + ]; + } + } + $i++; + } + + return $categ; + } + + function getGiftsInCategory(int $id, int $page = 1) + { + $this->requireUser(); + + if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce']) + $this->fail(105, "Commerce is disabled on this instance"); + + if(!(new GiftsRepo)->getCat($id)) + $this->fail(177, "Category not found"); + + $giftz = ((new GiftsRepo)->getCat($id))->getGifts($page); + $gifts = []; + + foreach($giftz as $gift) { + $gifts[] = [ + "name" => $gift->getName(), + "image" => $gift->getImage(2), + "usages_left" => (int)$gift->getUsagesLeft($this->getUser()), + "price" => $gift->getPrice(), # голосов + "is_free" => $gift->isFree() + ]; + } + + return $gifts; + } +} diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index 071ded81e..e21c8a095 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -2,6 +2,7 @@ namespace openvk\VKAPI\Handlers; use openvk\Web\Models\Repositories\Clubs as ClubsRepo; use openvk\Web\Models\Repositories\Users as UsersRepo; +use openvk\Web\Models\Entities\Club; final class Groups extends VKAPIRequestHandler { @@ -263,4 +264,271 @@ function leave(int $group_id) return 1; } + + function create(string $title, string $description = "", string $type = "group", int $public_category = 1, int $public_subcategory = 1, int $subtype = 1) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $club = new Club; + + $club->setName($title); + $club->setAbout($description); + $club->setOwner($this->getUser()->getId()); + $club->save(); + + $club->toggleSubscription($this->getUser()); + + return $this->getById((string)$club->getId()); + } + + function edit( + int $group_id, + string $title = NULL, + string $description = NULL, + string $screen_name = NULL, + string $website = NULL, + int $wall = NULL, + int $topics = NULL, + int $adminlist = NULL, + int $topicsAboveWall = NULL, + int $hideFromGlobalFeed = NULL) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $club = (new ClubsRepo)->get($group_id); + + if(!$club) $this->fail(203, "Club not found"); + if(!$club || !$club->canBeModifiedBy($this->getUser())) $this->fail(15, "You can't modify this group."); + if(!empty($screen_name) && !$club->setShortcode($screen_name)) $this->fail(103, "Invalid shortcode."); + + !is_null($title) ? $club->setName($title) : NULL; + !is_null($description) ? $club->setAbout($description) : NULL; + !is_null($screen_name) ? $club->setShortcode($screen_name) : NULL; + !is_null($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL; + !is_null($wall) ? $club->setWall($wall) : NULL; + !is_null($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL; + !is_null($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL; + !is_null($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL; + !is_null($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL; + + $club->save(); + + return 1; + } + + function getMembers(string $group_id, string $sort = "id_asc", int $offset = 0, int $count = 100, string $fields = "", string $filter = "any") + { + # bdate,can_post,can_see_all_posts,can_see_audio,can_write_private_message,city,common_count,connections,contacts,country,domain,education,has_mobile,last_seen,lists,online,online_mobile,photo_100,photo_200,photo_200_orig,photo_400_orig,photo_50,photo_max,photo_max_orig,relation,relatives,schools,sex,site,status,universities + $club = (new ClubsRepo)->get((int) $group_id); + if(!$club) + $this->fail(125, "Invalid group id"); + + $sorter = "follower ASC"; + + switch($sort) { + default: + case "time_asc": + case "id_asc": + $sorter = "follower ASC"; + break; + case "time_desc": + case "id_desc": + $sorter = "follower DESC"; + break; + } + + $members = array_slice(iterator_to_array($club->getFollowers(1, $count, $sorter)), $offset); + $arr = (object) [ + "count" => count($members), + "items" => array()]; + + $filds = explode(",", $fields); + + $i = 0; + foreach($members as $member) { + if($i > $count) { + break; + } + + $arr->items[] = (object) [ + "id" => $member->getId(), + "name" => $member->getCanonicalName(), + ]; + + foreach($filds as $fild) { + switch($fild) { + case "bdate": + $arr->items[$i]->bdate = $member->getBirthday()->format('%e.%m.%Y'); + break; + case "can_post": + $arr->items[$i]->can_post = $club->canBeModifiedBy($member); + break; + case "can_see_all_posts": + $arr->items[$i]->can_see_all_posts = 1; + break; + case "can_see_audio": + $arr->items[$i]->can_see_audio = 0; + break; + case "can_write_private_message": + $arr->items[$i]->can_write_private_message = 0; + break; + case "common_count": + $arr->items[$i]->common_count = 420; + break; + case "connections": + $arr->items[$i]->connections = 1; + break; + case "contacts": + $arr->items[$i]->contacts = $member->getContactEmail(); + break; + case "country": + $arr->items[$i]->country = 1; + break; + case "domain": + $arr->items[$i]->domain = ""; + break; + case "education": + $arr->items[$i]->education = ""; + break; + case "has_mobile": + $arr->items[$i]->has_mobile = false; + break; + case "last_seen": + $arr->items[$i]->last_seen = $member->getOnline()->timestamp(); + break; + case "lists": + $arr->items[$i]->lists = ""; + break; + case "online": + $arr->items[$i]->online = $member->isOnline(); + break; + case "online_mobile": + $arr->items[$i]->online_mobile = $member->getOnlinePlatform() == "android" || $member->getOnlinePlatform() == "iphone" || $member->getOnlinePlatform() == "mobile"; + break; + case "photo_100": + $arr->items[$i]->photo_100 = $member->getAvatarURL("tiny"); + break; + case "photo_200": + $arr->items[$i]->photo_200 = $member->getAvatarURL("normal"); + break; + case "photo_200_orig": + $arr->items[$i]->photo_200_orig = $member->getAvatarURL("normal"); + break; + case "photo_400_orig": + $arr->items[$i]->photo_400_orig = $member->getAvatarURL("normal"); + break; + case "photo_max": + $arr->items[$i]->photo_max = $member->getAvatarURL("original"); + break; + case "photo_max_orig": + $arr->items[$i]->photo_max_orig = $member->getAvatarURL(); + break; + case "relation": + $arr->items[$i]->relation = $member->getMaritalStatus(); + break; + case "relatives": + $arr->items[$i]->relatives = 0; + break; + case "schools": + $arr->items[$i]->schools = 0; + break; + case "sex": + $arr->items[$i]->sex = $member->isFemale() ? 1 : 2; + break; + case "site": + $arr->items[$i]->site = $member->getWebsite(); + break; + case "status": + $arr->items[$i]->status = $member->getStatus(); + break; + case "universities": + $arr->items[$i]->universities = 0; + break; + } + } + $i++; + } + return $arr; + } + + function getSettings(string $group_id) + { + $this->requireUser(); + $club = (new ClubsRepo)->get((int)$group_id); + + if(!$club || !$club->canBeModifiedBy($this->getUser())) + $this->fail(15, "You can't get settings of this group."); + + $arr = (object) [ + "title" => $club->getName(), + "description" => $club->getDescription() != NULL ? $club->getDescription() : "", + "address" => $club->getShortcode(), + "wall" => $club->canPost() == true ? 1 : 0, + "photos" => 1, + "video" => 0, + "audio" => 0, + "docs" => 0, + "topics" => $club->isEveryoneCanCreateTopics() == true ? 1 : 0, + "wiki" => 0, + "messages" => 0, + "obscene_filter" => 0, + "obscene_stopwords" => 0, + "obscene_words" => "", + "access" => 1, + "subject" => 1, + "subject_list" => [ + 0 => "в", + 1 => "опенвк", + 2 => "нет", + 3 => "категорий", + 4 => "групп", + ], + "rss" => "/club".$club->getId()."/rss", + "website" => $club->getWebsite(), + "age_limits" => 0, + "market" => [], + ]; + + return $arr; + } + + function isMember(string $group_id, int $user_id, string $user_ids = "", bool $extended = false) + { + $this->requireUser(); + $id = $user_id != NULL ? $user_id : explode(",", $user_ids); + + if($group_id < 0) + $this->fail(228, "Remove the minus from group_id"); + + $club = (new ClubsRepo)->get((int)$group_id); + $usver = (new UsersRepo)->get((int)$id); + + if(!$club || $group_id == 0) + $this->fail(203, "Invalid club"); + + if(!$usver || $usver->isDeleted() || $user_id == 0) + $this->fail(30, "Invalid user"); + + if($extended == false) { + return $club->getSubscriptionStatus($usver) ? 1 : 0; + } else { + return (object) + [ + "member" => $club->getSubscriptionStatus($usver) ? 1 : 0, + "request" => 0, + "invitation" => 0, + "can_invite" => 0, + "can_recall" => 0 + ]; + } + } + + function remove(int $group_id, int $user_id) + { + $this->requireUser(); + + $this->fail(501, "Not implemented"); + } } diff --git a/VKAPI/Handlers/Notes.php b/VKAPI/Handlers/Notes.php new file mode 100644 index 000000000..d3dc3468a --- /dev/null +++ b/VKAPI/Handlers/Notes.php @@ -0,0 +1,271 @@ +requireUser(); + $this->willExecuteWriteAction(); + + $note = new Note; + $note->setOwner($this->getUser()->getId()); + $note->setCreated(time()); + $note->setName($title); + $note->setSource($text); + $note->setEdited(time()); + $note->save(); + + return $note->getVirtualId(); + } + + function createComment(string $note_id, int $owner_id, string $message, int $reply_to = 0, string $attachments = "") + { + $this->requireUser(); + $this->willExecuteWriteAction(); + $note = (new NotesRepo)->getNoteById((int)$owner_id, (int)$note_id); + + if(!$note) + $this->fail(180, "Note not found"); + + if($note->isDeleted()) + $this->fail(189, "Note is deleted"); + + if($note->getOwner()->isDeleted()) + $this->fail(403, "Owner is deleted"); + + if(empty($message) && empty($attachments)) + $this->fail(100, "Required parameter 'message' missing."); + + $comment = new Comment; + $comment->setOwner($this->getUser()->getId()); + $comment->setModel(get_class($note)); + $comment->setTarget($note->getId()); + $comment->setContent($message); + $comment->setCreated(time()); + $comment->save(); + + if(!empty($attachments)) { + $attachmentsArr = explode(",", $attachments); + + if(sizeof($attachmentsArr) > 10) + $this->fail(50, "Error: too many attachments"); + + foreach($attachmentsArr as $attac) { + $attachmentType = NULL; + + if(str_contains($attac, "photo")) + $attachmentType = "photo"; + elseif(str_contains($attac, "video")) + $attachmentType = "video"; + else + $this->fail(205, "Unknown attachment type"); + + $attachment = str_replace($attachmentType, "", $attac); + + $attachmentOwner = (int)explode("_", $attachment)[0]; + $attachmentId = (int)end(explode("_", $attachment)); + + $attacc = NULL; + + if($attachmentType == "photo") { + $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Photo does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this photo"); + + $comment->attach($attacc); + } elseif($attachmentType == "video") { + $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Video does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this video"); + + $comment->attach($attacc); + } + } + } + + return $comment->getId(); + } + + function delete(string $note_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $note = (new NotesRepo)->get((int)$note_id); + + if(!$note) + $this->fail(180, "Note not found"); + + if(!$note->canBeModifiedBy($this->getUser())) + $this->fail(15, "Access to note denied"); + + $note->delete(); + + return 1; + } + + function deleteComment(int $comment_id, int $owner_id = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $comment = (new CommentsRepo)->get($comment_id); + + if(!$comment || !$comment->canBeDeletedBy($this->getUser())) + $this->fail(403, "Access to comment denied"); + + $comment->delete(); + + return 1; + } + + function edit(string $note_id, string $title = "", string $text = "", int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "") + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $note = (new NotesRepo)->getNoteById($this->getUser()->getId(), (int)$note_id); + + if(!$note) + $this->fail(180, "Note not found"); + + if($note->isDeleted()) + $this->fail(189, "Note is deleted"); + + if(!$note->canBeModifiedBy($this->getUser())) + $this->fail(403, "No access"); + + !empty($title) ? $note->setName($title) : NULL; + !empty($text) ? $note->setSource($text) : NULL; + + $note->setCached_Content(NULL); + $note->setEdited(time()); + $note->save(); + + return 1; + } + + function editComment(int $comment_id, string $message, int $owner_id = NULL) + { + /* + $this->requireUser(); + $this->willExecuteWriteAction(); + + $comment = (new CommentsRepo)->get($comment_id); + + if($comment->getOwner() != $this->getUser()->getId()) + $this->fail(15, "Access to comment denied"); + + $comment->setContent($message); + $comment->setEdited(time()); + $comment->save(); + */ + + return 1; + } + + function get(int $user_id, string $note_ids = "", int $offset = 0, int $count = 10, int $sort = 0) + { + $this->requireUser(); + $user = (new UsersRepo)->get($user_id); + + if(!$user || $user->isDeleted()) + $this->fail(15, "Invalid user"); + + if(empty($note_ids)) { + $notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset); + $nodez = (object) [ + "count" => (new NotesRepo)->getUserNotesCount((new UsersRepo)->get($user_id)), + "notes" => [] + ]; + + foreach($notes as $note) { + if($note->isDeleted()) continue; + + $nodez->notes[] = $note->toVkApiStruct(); + } + } else { + $notes = explode(',', $note_ids); + + foreach($notes as $note) + { + $id = explode("_", $note); + + $items = []; + + $note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]); + if($note) { + $nodez->notes[] = $note->toVkApiStruct(); + } + } + } + + return $nodez; + } + + function getById(int $note_id, int $owner_id, bool $need_wiki = false) + { + $this->requireUser(); + + $note = (new NotesRepo)->getNoteById($owner_id, $note_id); + + if(!$note) + $this->fail(180, "Note not found"); + + if($note->isDeleted()) + $this->fail(189, "Note is deleted"); + + if(!$note->getOwner() || $note->getOwner()->isDeleted()) + $this->fail(177, "Owner does not exists"); + + return $note->toVkApiStruct(); + } + + function getComments(int $note_id, int $owner_id, int $sort = 1, int $offset = 0, int $count = 100) + { + $this->requireUser(); + + $note = (new NotesRepo)->getNoteById($owner_id, $note_id); + + if(!$note) + $this->fail(180, "Note not found"); + + if($note->isDeleted()) + $this->fail(189, "Note is deleted"); + + if(!$note->getOwner()) + $this->fail(177, "Owner does not exists"); + + $arr = (object) [ + "count" => $note->getCommentsCount(), + "comments" => []]; + $comments = array_slice(iterator_to_array($note->getComments(1, $count + $offset)), $offset); + + foreach($comments as $comment) { + $arr->comments[] = $comment->toVkApiStruct($this->getUser(), false, false, $note); + } + + return $arr; + } + + function getFriendsNotes(int $offset = 0, int $count = 0) + { + $this->fail(501, "Not implemented"); + } + + function restoreComment(int $comment_id = 0, int $owner_id = 0) + { + $this->fail(501, "Not implemented"); + } +} diff --git a/VKAPI/Handlers/Photos.php b/VKAPI/Handlers/Photos.php index 9a02dd8cf..4ab56832c 100644 --- a/VKAPI/Handlers/Photos.php +++ b/VKAPI/Handlers/Photos.php @@ -3,9 +3,12 @@ use Nette\InvalidStateException; use Nette\Utils\ImageException; -use openvk\Web\Models\Entities\Photo; +use openvk\Web\Models\Entities\{Photo, Album, Comment}; use openvk\Web\Models\Repositories\Albums; +use openvk\Web\Models\Repositories\Photos as PhotosRepo; use openvk\Web\Models\Repositories\Clubs; +use openvk\Web\Models\Repositories\Users as UsersRepo; +use openvk\Web\Models\Repositories\Comments as CommentsRepo; final class Photos extends VKAPIRequestHandler { @@ -227,4 +230,504 @@ function save(string $photos_list, string $hash, int $album_id = 0, ?string $cap "items" => $images, ]; } + + function createAlbum(string $title, int $group_id = 0, string $description = "", int $privacy = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if($group_id != 0) { + $club = (new Clubs)->get((int) $group_id); + + if(!$club || !$club->canBeModifiedBy($this->getUser())) { + $this->fail(20, "Invalid club"); + } + } + + $album = new Album; + $album->setOwner(isset($club) ? $club->getId() * -1 : $this->getUser()->getId()); + $album->setName($title); + $album->setDescription($description); + $album->setCreated(time()); + $album->save(); + + return $album->toVkApiStruct($this->getUser()); + } + + function editAlbum(int $album_id, int $owner_id, string $title, string $description = "", int $privacy = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id); + + if(!$album || $album->isDeleted()) { + $this->fail(2, "Invalid album"); + } + + if(empty($title)) { + $this->fail(25, "Title is empty"); + } + + if($album->isCreatedBySystem()) { + $this->fail(40, "You can't change system album"); + } + + if(!$album->canBeModifiedBy($this->getUser())) { + $this->fail(2, "Access to album denied"); + } + + $album->setName($title); + $album->setDescription($description); + + $album->save(); + + return $album->toVkApiStruct($this->getUser()); + } + + function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $res = []; + + if(empty($album_ids)) { + if($owner_id > 0) { + $user = (new UsersRepo)->get($owner_id); + + $res = [ + "count" => (new Albums)->getUserAlbumsCount($user), + "items" => [] + ]; + + if(!$user || $user->isDeleted()) + $this->fail(2, "Invalid user"); + + + if(!$user->getPrivacyPermission('photos.read', $this->getUser())) + $this->fail(21, "This user chose to hide his albums."); + + $albums = array_slice(iterator_to_array((new Albums)->getUserAlbums($user, 1, $count + $offset)), $offset); + + foreach($albums as $album) { + if(!$need_system && $album->isCreatedBySystem()) continue; + $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); + } + } + + else { + $club = (new Clubs)->get($owner_id * -1); + + $res = [ + "count" => (new Albums)->getClubAlbumsCount($club), + "items" => [] + ]; + + if(!$club) + $this->fail(2, "Invalid club"); + + $albums = array_slice(iterator_to_array((new Albums)->getClubAlbums($club, 1, $count + $offset)), $offset); + + foreach($albums as $album) { + if(!$need_system && $album->isCreatedBySystem()) continue; + $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); + } + } + + } else { + $albums = explode(',', $album_ids); + + $res = [ + "count" => sizeof($albums), + "items" => [] + ]; + + foreach($albums as $album) + { + $id = explode("_", $album); + + $album = (new Albums)->getAlbumByOwnerAndId((int)$id[0], (int)$id[1]); + if($album && !$album->isDeleted()) { + if(!$need_system && $album->isCreatedBySystem()) continue; + $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); + } + } + } + + return $res; + } + + function getAlbumsCount(int $user_id = 0, int $group_id = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) { + $this->fail(21, "Select user_id or group_id"); + } + + if($user_id > 0) { + + $us = (new UsersRepo)->get($user_id); + if(!$us || $us->isDeleted()) { + $this->fail(21, "Invalid user"); + } + + if(!$us->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(21, "This user chose to hide his albums."); + } + + return (new Albums)->getUserAlbumsCount($us); + } + + if($group_id > 0) + { + $cl = (new Clubs)->get($group_id); + if(!$cl) { + $this->fail(21, "Invalid club"); + } + + return (new Albums)->getClubAlbumsCount($cl); + } + } + + function getById(string $photos, bool $extended = false, bool $photo_sizes = false) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $phts = explode(",", $photos); + $res = []; + + foreach($phts as $phota) { + $ph = explode("_", $phota); + $photo = (new PhotosRepo)->getByOwnerAndVID((int)$ph[0], (int)$ph[1]); + + if(!$photo || $photo->isDeleted()) { + $this->fail(21, "Invalid photo"); + } + + if($photo->getOwner()->isDeleted()) { + $this->fail(21, "Owner of this photo is deleted"); + } + + if(!$photo->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(21, "This user chose to hide his photos."); + } + + $res[] = $photo->toVkApiStruct($photo_sizes, $extended); + } + + return $res; + } + + function get(int $owner_id, int $album_id, string $photo_ids = "", bool $extended = false, bool $photo_sizes = false, int $offset = 0, int $count = 10) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $res = []; + + if(empty($photo_ids)) { + $album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id); + + if(!$album->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(21, "This user chose to hide his albums."); + } + + if(!$album || $album->isDeleted()) { + $this->fail(21, "Invalid album"); + } + + $photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset); + $res["count"] = sizeof($photos); + + foreach($photos as $photo) { + if(!$photo || $photo->isDeleted()) continue; + $res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended); + } + + } else { + $photos = explode(',', $photo_ids); + + $res = [ + "count" => sizeof($photos), + "items" => [] + ]; + + foreach($photos as $photo) + { + $id = explode("_", $photo); + + $phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]); + if($phot && !$phot->isDeleted()) { + $res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended); + } + } + } + + return $res; + } + + function deleteAlbum(int $album_id, int $group_id = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $album = (new Albums)->get($album_id); + + if(!$album || $album->canBeModifiedBy($this->getUser())) { + $this->fail(21, "Invalid album"); + } + + if($album->isDeleted()) { + $this->fail(22, "Album already deleted"); + } + + $album->delete(); + + return 1; + } + + function edit(int $owner_id, int $photo_id, string $caption = "") + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); + + if(!$photo) { + $this->fail(21, "Invalid photo"); + } + + if($photo->isDeleted()) { + $this->fail(21, "Photo is deleted"); + } + + if(!empty($caption)) { + $photo->setDescription($caption); + $photo->save(); + } + + return 1; + } + + function delete(int $owner_id, int $photo_id, string $photos = "") + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if(empty($photos)) { + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); + + if($this->getUser()->getId() !== $photo->getOwner()->getId()) { + $this->fail(21, "You can't delete another's photo"); + } + + if(!$photo) { + $this->fail(21, "Invalid photo"); + } + + if($photo->isDeleted()) { + $this->fail(21, "Photo already deleted"); + } + + $photo->delete(); + } else { + $photozs = explode(',', $photos); + + foreach($photozs as $photo) + { + $id = explode("_", $photo); + + $phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]); + + if($this->getUser()->getId() !== $phot->getOwner()->getId()) { + $this->fail(21, "You can't delete another's photo"); + } + + if(!$phot) { + $this->fail(21, "Invalid photo"); + } + + if($phot->isDeleted()) { + $this->fail(21, "Photo already deleted"); + } + + $phot->delete(); + } + } + + return 1; + } + + function getAllComments(int $owner_id, int $album_id, bool $need_likes = false, int $offset = 0, int $count = 100) + { + $this->fail(501, "Not implemented"); + } + + function deleteComment(int $comment_id, int $owner_id = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $comment = (new CommentsRepo)->get($comment_id); + if(!$comment) { + $this->fail(21, "Invalid comment"); + } + + if(!$comment->canBeModifiedBy($this->getUser())) { + $this->fail(21, "Forbidden"); + } + + if($comment->isDeleted()) { + $this->fail(4, "Comment already deleted"); + } + + $comment->delete(); + + return 1; + } + + function createComment(int $owner_id, int $photo_id, string $message = "", string $attachments = "", bool $from_group = false) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if(empty($message) && empty($attachments)) { + $this->fail(100, "Required parameter 'message' missing."); + } + + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); + + if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(21, "This user chose to hide his albums."); + } + + if(!$photo) + $this->fail(180, "Photo not found"); + if($photo->isDeleted()) + $this->fail(189, "Photo is deleted"); + + $comment = new Comment; + $comment->setOwner($this->getUser()->getId()); + $comment->setModel(get_class($photo)); + $comment->setTarget($photo->getId()); + $comment->setContent($message); + $comment->setCreated(time()); + $comment->save(); + + if(!empty($attachments)) { + $attachmentsArr = explode(",", $attachments); + + if(sizeof($attachmentsArr) > 10) + $this->fail(50, "Error: too many attachments"); + + foreach($attachmentsArr as $attac) { + $attachmentType = NULL; + + if(str_contains($attac, "photo")) + $attachmentType = "photo"; + elseif(str_contains($attac, "video")) + $attachmentType = "video"; + else + $this->fail(205, "Unknown attachment type"); + + $attachment = str_replace($attachmentType, "", $attac); + + $attachmentOwner = (int)explode("_", $attachment)[0]; + $attachmentId = (int)end(explode("_", $attachment)); + + $attacc = NULL; + + if($attachmentType == "photo") { + $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Photo does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this photo"); + + $comment->attach($attacc); + } elseif($attachmentType == "video") { + $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Video does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this video"); + + $comment->attach($attacc); + } + } + } + + return $comment->getId(); + } + + function getAll(int $owner_id, bool $extended = false, int $offset = 0, int $count = 100, bool $photo_sizes = false) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if($owner_id < 0) { + $this->fail(4, "This method doesn't works with clubs"); + } + + $user = (new UsersRepo)->get($owner_id); + + if(!$user) { + $this->fail(4, "Invalid user"); + } + + if(!$user->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(21, "This user chose to hide his albums."); + } + + $photos = array_slice(iterator_to_array((new PhotosRepo)->getEveryUserPhoto($user, 1, $count + $offset)), $offset); + $res = []; + + foreach($photos as $photo) { + if(!$photo || $photo->isDeleted()) continue; + $res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended); + } + + return $res; + } + + function getComments(int $owner_id, int $photo_id, bool $need_likes = false, int $offset = 0, int $count = 100, bool $extended = false, string $fields = "") + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); + $comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset); + + if(!$photo) { + $this->fail(4, "Invalid photo"); + } + + if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(21, "This user chose to hide his photos."); + } + + if($photo->isDeleted()) { + $this->fail(4, "Photo is deleted"); + } + + $res = [ + "count" => sizeof($comms), + "items" => [] + ]; + + foreach($comms as $comment) { + $res["items"][] = $comment->toVkApiStruct($this->getUser(), $need_likes, $extended); + if($extended) { + if($comment->getOwner() instanceof \openvk\Web\Models\Entities\User) { + $res["profiles"][] = $comment->getOwner()->toVkApiStruct(); + } + } + } + + return $res; + } } \ No newline at end of file diff --git a/VKAPI/Handlers/Status.php b/VKAPI/Handlers/Status.php new file mode 100644 index 000000000..843f42bdc --- /dev/null +++ b/VKAPI/Handlers/Status.php @@ -0,0 +1,35 @@ +requireUser(); + if($user_id == 0 && $group_id == 0) { + return $this->getUser()->getStatus(); + } else { + if($group_id > 0) + $this->fail(501, "Group statuses are not implemented"); + else + return (new UsersRepo)->get($user_id)->getStatus(); + } + } + + function set(string $text, int $group_id = 0) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if($group_id > 0) { + $this->fail(501, "Group statuses are not implemented"); + } else { + $this->getUser()->setStatus($text); + $this->getUser()->save(); + + return 1; + } + } +} diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index dcf63a26a..ee07f3c12 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -9,6 +9,10 @@ use openvk\Web\Models\Repositories\Posts as PostsRepo; use openvk\Web\Models\Entities\Comment; use openvk\Web\Models\Repositories\Comments as CommentsRepo; +use openvk\Web\Models\Entities\Photo; +use openvk\Web\Models\Repositories\Photos as PhotosRepo; +use openvk\Web\Models\Entities\Video; +use openvk\Web\Models\Repositories\Videos as VideosRepo; final class Wall extends VKAPIRequestHandler { @@ -367,7 +371,7 @@ function getById(string $posts, int $extended = 0, string $fields = "", User $us ]; } - function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object + function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = ""): object { $this->requireUser(); $this->willExecuteWriteAction(); @@ -405,27 +409,7 @@ function post(string $owner_id, string $message = "", int $from_group = 0, int $ if($signed == 1) $flags |= 0b01000000; - # TODO: Compatible implementation of this - try { - $photo = NULL; - $video = NULL; - if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) { - $album = NULL; - if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId()) - $album = (new AlbumsRepo)->getUserWallAlbum($wallOwner); - - $photo = Photo::fastMake($this->getUser()->getId(), $message, $_FILES["photo"], $album, $anon); - } - - if($_FILES["video"]["error"] === UPLOAD_ERR_OK) - $video = Video::fastMake($this->getUser()->getId(), $_FILES["video"]["name"], $message, $_FILES["video"], $anon); - } catch(\DomainException $ex) { - $this->fail(-156, "The media file is corrupted"); - } catch(ISE $ex) { - $this->fail(-156, "The media file is corrupted or too large "); - } - - if(empty($message) && !$photo && !$video) + if(empty($message) && empty($attachments)) $this->fail(100, "Required parameter 'message' missing."); try { @@ -441,11 +425,50 @@ function post(string $owner_id, string $message = "", int $from_group = 0, int $ $this->fail(100, "One of the parameters specified was missing or invalid"); } - if(!is_null($photo)) - $post->attach($photo); + if(!empty($attachments)) { + $attachmentsArr = explode(",", $attachments); + # Аттачи такого вида: [тип][id владельца]_[id вложения] + # Пример: photo1_1 + + if(sizeof($attachmentsArr) > 10) + $this->fail(50, "Error: too many attachments"); + + foreach($attachmentsArr as $attac) { + $attachmentType = NULL; - if(!is_null($video)) - $post->attach($video); + if(str_contains($attac, "photo")) + $attachmentType = "photo"; + elseif(str_contains($attac, "video")) + $attachmentType = "video"; + else + $this->fail(205, "Unknown attachment type"); + + $attachment = str_replace($attachmentType, "", $attac); + + $attachmentOwner = (int)explode("_", $attachment)[0]; + $attachmentId = (int)end(explode("_", $attachment)); + + $attacc = NULL; + + if($attachmentType == "photo") { + $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Photo does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this photo"); + + $post->attach($attacc); + } elseif($attachmentType == "video") { + $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Video does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this video"); + + $post->attach($attacc); + } + } + } if($wall > 0 && $wall !== $this->user->identity->getId()) (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); @@ -632,16 +655,20 @@ function getComment(int $owner_id, int $comment_id, bool $extended = false, stri return $response; } - function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0) { + function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0, string $attachments = "") { $this->requireUser(); $this->willExecuteWriteAction(); $post = (new PostsRepo)->getPostById($owner_id, $post_id); - if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid"); + if(!$post || $post->isDeleted()) $this->fail(100, "Invalid post"); if($post->getTargetWall() < 0) $club = (new ClubsRepo)->get(abs($post->getTargetWall())); + if(empty($message) && empty($attachments)) { + $this->fail(100, "Required parameter 'message' missing."); + } + $flags = 0; if($from_group != 0 && !is_null($club) && $club->canBeModifiedBy($this->user)) $flags |= 0b10000000; @@ -659,6 +686,49 @@ function createComment(int $owner_id, int $post_id, string $message, int $from_g $this->fail(1, "ошибка про то что коммент большой слишком"); } + if(!empty($attachments)) { + $attachmentsArr = explode(",", $attachments); + + if(sizeof($attachmentsArr) > 10) + $this->fail(50, "Error: too many attachments"); + + foreach($attachmentsArr as $attac) { + $attachmentType = NULL; + + if(str_contains($attac, "photo")) + $attachmentType = "photo"; + elseif(str_contains($attac, "video")) + $attachmentType = "video"; + else + $this->fail(205, "Unknown attachment type"); + + $attachment = str_replace($attachmentType, "", $attac); + + $attachmentOwner = (int)explode("_", $attachment)[0]; + $attachmentId = (int)end(explode("_", $attachment)); + + $attacc = NULL; + + if($attachmentType == "photo") { + $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Photo does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this photo"); + + $comment->attach($attacc); + } elseif($attachmentType == "video") { + $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Video does not exists"); + if($attacc->getOwner()->getId() != $this->getUser()->getId()) + $this->fail(43, "You do not have access to this video"); + + $comment->attach($attacc); + } + } + } + if($post->getOwner()->getId() !== $this->user->getId()) if(($owner = $post->getOwner()) instanceof User) (new CommentNotification($owner, $comment, $post, $this->user))->emit(); diff --git a/VKAPI/README.md b/VKAPI/README.md index d4db764c0..75918afcb 100644 --- a/VKAPI/README.md +++ b/VKAPI/README.md @@ -5,7 +5,7 @@ exceptions. It is still a work-in-progress functionality. **Note**: requests to API are routed through openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers. -[Documentation for API clients](https://docs.openvk.su/openvk_engine/api/description/) +[Documentation for API clients](https://docs.openvk.uk/openvk_engine/api/description/) ## Implementing API methods diff --git a/Web/Models/Entities/Album.php b/Web/Models/Entities/Album.php index b20f03882..150cc6257 100644 --- a/Web/Models/Entities/Album.php +++ b/Web/Models/Entities/Album.php @@ -66,4 +66,31 @@ function hasPhoto(Photo $photo): bool { return $this->has($photo); } + + function toVkApiStruct(?User $user = NULL, bool $need_covers = false, bool $photo_sizes = false): object + { + $res = (object) []; + + $res->id = $this->getPrettyId(); + $res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0; + $res->owner_id = $this->getOwner()->getId(); + $res->title = $this->getName(); + $res->description = $this->getDescription(); + $res->created = $this->getCreationTime()->timestamp(); + $res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : NULL; + $res->size = $this->size(); + $res->privacy_comment = 1; + $res->upload_by_admins_only = 1; + $res->comments_disabled = 0; + $res->can_upload = $this->canBeModifiedBy($user); # thisUser недоступен в entities + if($need_covers) { + $res->thumb_src = $this->getCoverURL(); + + if($photo_sizes) { + $res->sizes = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getVkApiSizes() : NULL; + } + } + + return $res; + } } diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index db5baa888..b321a4761 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -160,7 +160,7 @@ function isBanned(): bool function canPost(): bool { - return (bool) $this->getRecord()->wall; + return (bool) $this->getRecord()->wall; } @@ -262,12 +262,12 @@ function getSubscriptionStatus(User $user): bool return $subbed && ($this->getOpennesStatus() === static::CLOSED ? $this->isSubscriptionAccepted($user) : true); } - function getFollowersQuery(): GroupedSelection + function getFollowersQuery(string $sort = "follower ASC"): GroupedSelection { $query = $this->getRecord()->related("subscriptions.target"); if($this->getOpennesStatus() === static::OPEN) { - $query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club"); + $query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club")->order($sort); } else { return false; } @@ -280,9 +280,9 @@ function getFollowersCount(): int return sizeof($this->getFollowersQuery()); } - function getFollowers(int $page = 1): \Traversable + function getFollowers(int $page = 1, int $perPage = 6, string $sort = "follower ASC"): \Traversable { - $rels = $this->getFollowersQuery()->page($page, 6); + $rels = $this->getFollowersQuery($sort)->page($page, $perPage); foreach($rels as $rel) { $rel = (new Users)->get($rel->follower); @@ -360,6 +360,35 @@ function getAlert(): ?string return $this->getRecord()->alert; } + function toVkApiStruct(?User $user = NULL): object + { + $res = []; + + $res->id = $this->getId(); + $res->name = $this->getName(); + $res->screen_name = $this->getShortCode(); + $res->is_closed = 0; + $res->deactivated = NULL; + $res->is_admin = $this->canBeModifiedBy($user); + + if($this->canBeModifiedBy($user)) { + $res->admin_level = 3; + } + + $res->is_member = $this->getSubscriptionStatus($user) ? 1 : 0; + + $res->type = "group"; + $res->photo_50 = $this->getAvatarUrl("miniscule"); + $res->photo_100 = $this->getAvatarUrl("tiny"); + $res->photo_200 = $this->getAvatarUrl("normal"); + + $res->can_create_topic = $this->canBeModifiedBy($user) ? 1 : $this->isEveryoneCanCreateTopics() ? 1 : 0; + + $res->can_post = $this->canBeModifiedBy($user) ? 1 : $this->canPost() ? 1 : 0; + + return (object) $res; + } + use Traits\TBackDrops; use Traits\TSubscribable; } diff --git a/Web/Models/Entities/Comment.php b/Web/Models/Entities/Comment.php index 9115ad3ec..bd833a82a 100644 --- a/Web/Models/Entities/Comment.php +++ b/Web/Models/Entities/Comment.php @@ -2,6 +2,7 @@ namespace openvk\Web\Models\Entities; use openvk\Web\Models\Repositories\Clubs; use openvk\Web\Models\RowModel; +use openvk\Web\Models\Entities\{Note}; class Comment extends Post { @@ -52,4 +53,36 @@ function canBeDeletedBy(User $user): bool $this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) || $this->getTarget() instanceof Topic && $this->getTarget()->canBeModifiedBy($user); } + + function toVkApiStruct(?User $user = NULL, bool $need_likes = false, bool $extended = false, ?Note $note = NULL): object + { + $res = (object) []; + + $res->id = $this->getId(); + $res->from_id = $this->getOwner()->getId(); + $res->date = $this->getPublicationTime()->timestamp(); + $res->text = $this->getText(); + $res->attachments = []; + $res->parents_stack = []; + + if(!is_null($note)) { + $res->uid = $this->getOwner()->getId(); + $res->nid = $note->getId(); + $res->oid = $note->getOwner()->getId(); + } + + foreach($this->getChildren() as $attachment) { + if($attachment->isDeleted()) + continue; + + $res->attachments[] = $attachment->toVkApiStruct(); + } + + if($need_likes) { + $res->count = $this->getLikesCount(); + $res->user_likes = (int)$this->hasLikeFrom($user); + $res->can_like = 1; + } + return $res; + } } diff --git a/Web/Models/Entities/Note.php b/Web/Models/Entities/Note.php index a536602a7..d259c2a50 100644 --- a/Web/Models/Entities/Note.php +++ b/Web/Models/Entities/Note.php @@ -118,4 +118,23 @@ function getSource(): string { return $this->getRecord()->source; } + + function toVkApiStruct(): object + { + $res = (object) []; + + $res->id = $this->getId(); + $res->owner_id = $this->getOwner()->getId(); + $res->title = $this->getName(); + $res->text = $this->getText(); + $res->date = $this->getPublicationTime()->timestamp(); + $res->comments = $this->getCommentsCount(); + $res->read_comments = $this->getCommentsCount(); + $res->view_url = "/note".$this->getOwner()->getId()."_".$this->getId(); + $res->privacy_view = 1; + $res->can_comment = 1; + $res->text_wiki = "r"; + + return $res; + } } diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index 3c4db8864..29526daaf 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -296,25 +296,35 @@ function getAlbum(): ?Album return (new Albums)->getAlbumByPhotoId($this); } - function toVkApiStruct(): object + function toVkApiStruct(bool $photo_sizes = true, bool $extended = false): object { $res = (object) []; $res->id = $res->pid = $this->getId(); - $res->owner_id = $res->user_id = $this->getOwner()->getId()->getId(); + $res->owner_id = $res->user_id = $this->getOwner()->getId(); $res->aid = $res->album_id = NULL; $res->width = $this->getDimensions()[0]; $res->height = $this->getDimensions()[1]; $res->date = $res->created = $this->getPublicationTime()->timestamp(); - $res->sizes = $this->getVkApiSizes(); - $res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule"); - $res->src = $res->photo_130 = $this->getURLBySizeId("tiny"); - $res->src_big = $res->photo_604 = $this->getURLBySizeId("normal"); - $res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large"); - $res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger"); - $res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original"); - $res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES"); + if($photo_sizes) { + $res->sizes = $this->getVkApiSizes(); + $res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule"); + $res->src = $res->photo_130 = $this->getURLBySizeId("tiny"); + $res->src_big = $res->photo_604 = $this->getURLBySizeId("normal"); + $res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large"); + $res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger"); + $res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original"); + $res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES"); + } + + if($extended) { + $res->likes = $this->getLikesCount(); # их нету но пусть будут + $res->comments = $this->getCommentsCount(); + $res->tags = 0; + $res->can_comment = 1; + $res->can_repost = 0; + } return $res; } diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index ed1e3e601..348631e42 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -1109,6 +1109,23 @@ function canUnbanThemself(): bool return true; } + + function toVkApiStruct(): object + { + $res = (object) []; + + $res->id = $this->getId(); + $res->first_name = $this->getFirstName(); + $res->last_name = $this->getLastName(); + $res->deactivated = $this->isDeactivated(); + $res->photo_50 = $this->getAvatarURL(); + $res->photo_100 = $this->getAvatarURL("tiny"); + $res->photo_200 = $this->getAvatarURL("normal"); + $res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL; + # TODO: Perenesti syuda vsyo ostalnoyie + + return $res; + } use Traits\TBackDrops; use Traits\TSubscribable; diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index 45320efc2..4c652fddf 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -165,6 +165,11 @@ function getApiStructure(): object ]; } + function toVkApiStruct(): object + { + return $this->getApiStructure(); + } + function setLink(string $link): string { if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) { diff --git a/Web/Models/Repositories/Albums.php b/Web/Models/Repositories/Albums.php index 99c6c7324..b38ee4627 100644 --- a/Web/Models/Repositories/Albums.php +++ b/Web/Models/Repositories/Albums.php @@ -123,4 +123,14 @@ function getAlbumByPhotoId(Photo $photo): ?Album return $dbalbum->collection ? $this->get($dbalbum->collection) : null; } + + function getAlbumByOwnerAndId(int $owner, int $id) + { + $album = $this->albums->where([ + "owner" => $owner, + "id" => $id + ])->fetch(); + + return new Album($album); + } } diff --git a/Web/Models/Repositories/Notes.php b/Web/Models/Repositories/Notes.php index 7d95f9753..0473070a6 100644 --- a/Web/Models/Repositories/Notes.php +++ b/Web/Models/Repositories/Notes.php @@ -26,10 +26,10 @@ function get(int $id): ?Note return $this->toNote($this->notes->get($id)); } - function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL): \Traversable + function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL, string $sort = "DESC"): \Traversable { $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; - foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created DESC")->page($page, $perPage) as $album) + foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created $sort")->page($page, $perPage) as $album) yield new Note($album); } diff --git a/Web/Models/Repositories/Photos.php b/Web/Models/Repositories/Photos.php index 4ff8a1b95..88c7e804c 100644 --- a/Web/Models/Repositories/Photos.php +++ b/Web/Models/Repositories/Photos.php @@ -1,6 +1,6 @@ photos->where([ + "owner" => $user->getId() + ]); + + foreach($photos as $photo) { + yield new Photo($photo); + } + } } diff --git a/Web/Models/Repositories/Videos.php b/Web/Models/Repositories/Videos.php index 307533715..632733496 100644 --- a/Web/Models/Repositories/Videos.php +++ b/Web/Models/Repositories/Videos.php @@ -58,7 +58,7 @@ function find(string $query = "", array $pars = [], string $sort = "id"): Util\E else $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; - $result = $this->videos->where("name OR description LIKE ?", $query)->where("deleted", 0); + $result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0); $nnparamsCount = sizeof($notNullParams); if($nnparamsCount > 0) { diff --git a/Web/Presenters/AboutPresenter.php b/Web/Presenters/AboutPresenter.php index aed1d84a2..9115a019b 100644 --- a/Web/Presenters/AboutPresenter.php +++ b/Web/Presenters/AboutPresenter.php @@ -141,6 +141,6 @@ function renderHumansTxt(): void function renderDev(): void { - $this->redirect("https://docs.openvk.su/"); + $this->redirect("https://docs.openvk.uk/"); } } diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml index 788bba8d5..7b1a30f3a 100644 --- a/Web/Presenters/templates/Admin/@layout.xml +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -6,6 +6,7 @@ {include title} - {_admin} {$instance_name} @@ -21,12 +22,49 @@ -
+
@@ -34,7 +72,7 @@
-
+
- {$comment->getPublicationTime()} | + {var $target = "wall"} + + {if get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Note"} + {php $target = "note"} + {elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Post"} + {php $target = "wall"} + {elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Photo"} + {php $target = "photo"} + {elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Video"} + {php $target = "video"} + {elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Topic"} + {php $target = "topic"} + {/if} + + {$comment->getPublicationTime()} | {if $comment->canBeDeletedBy($thisUser)} {_delete} | {/if} diff --git a/Web/static/css/main.css b/Web/static/css/main.css index d73194d7f..2fef267cd 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -2482,5 +2482,5 @@ a.poll-retract-vote { } .page_content.overscrolled div[class$="_big_block"] { - width: unset; + width: 100%; } diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js index 938e712c8..8b248ac64 100644 --- a/Web/static/js/openvk.cls.js +++ b/Web/static/js/openvk.cls.js @@ -674,7 +674,7 @@ function resetSearch() for(const input of inputs) { - if(input != dnt) { + if(input != dnt && input != gend && input != gend1 && input != gend2) { input.value = "" } } diff --git a/Web/static/js/package.json b/Web/static/js/package.json index a082506ba..f9459ce4c 100644 --- a/Web/static/js/package.json +++ b/Web/static/js/package.json @@ -1,9 +1,9 @@ { "dependencies": { - "@atlassian/aui": "^8.5.1", + "@atlassian/aui": "^9.6.0", "create-react-class": "^15.7.0", "handlebars": "^4.7.7", - "jquery": "^2.1.0", + "jquery": "^3.0.0", "knockout": "^3.5.1", "ky": "^0.19.0", "literallycanvas": "^0.5.2", diff --git a/Web/static/js/yarn.lock b/Web/static/js/yarn.lock index 7560c6648..fa61ed9b7 100644 --- a/Web/static/js/yarn.lock +++ b/Web/static/js/yarn.lock @@ -2,34 +2,29 @@ # yarn lockfile v1 -"@atlassian/aui@^8.5.1": - version "8.5.1" - resolved "https://registry.yarnpkg.com/@atlassian/aui/-/aui-8.5.1.tgz#6c585674240621a007e59b6ca8c79f45a335738d" - integrity sha512-u+cWuOTpDyNod5B1SZnDCa57ruad54jQ/4vUd2VGDdp8qqiYLeuEush1deuKejvFFWSS6zd7KhkzmO/m0tm1aw== +"@atlassian/aui@^9.6.0": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@atlassian/aui/-/aui-9.6.0.tgz#9f35e67359022f1e6d5efa2653e79aeec95b9e77" + integrity sha512-o/bCufj0tUU6pRk3AWoXlcyVMTMx4QswB1UY5oJWSjopA+z/QUx0fhc4rRIIbxP0MrJMNRDpgPyuzkoPb7Z7ow== dependencies: - "@atlassian/brand-logos" "^1.2.0" - "@atlassian/tipsy" "^1.3.1" - backbone "^1.3.3" - css.escape "1.5.0" - fancy-file-input "~2.0.4" - jquery-ui "^1.12.1" - popper.js "^1.14.5" + "@atlassian/tipsy" "1.3.3" + "@popperjs/core" "2.11.6" + backbone "1.4.1" + css.escape "1.5.1" + dompurify "2.4.5" + fancy-file-input "2.0.4" + jquery-ui "1.13.2" skatejs "0.13.17" skatejs-template-html "0.0.0" trim-extra-html-whitespace "1.3.0" - underscore "^1.9.1" + underscore "1.13.6" -"@atlassian/brand-logos@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@atlassian/brand-logos/-/brand-logos-1.2.0.tgz#2110dba6ca49acaf1c51b15420a4567b01b4b4bd" - integrity sha512-rtTIQqvZO6Tr7+oNWvtED7xMpuE1NQoNF5vA1d+8ym3wTANvgbKxyHK2pj5jTW3dMoEE2Qac8YMebtN46B/mQg== +"@atlassian/tipsy@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@atlassian/tipsy/-/tipsy-1.3.3.tgz#3f77754c4c70324c5c938e41abaa2ca682f22036" + integrity sha512-6jd9wdoiPdCbwsNi1Xrn/oMdGz22dKPeCoZ/cCGKqjnh+UYkBKb5W3spW+WNqRSxGvVtfUEEg6TXotRK/FPDaw== -"@atlassian/tipsy@^1.3.1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@atlassian/tipsy/-/tipsy-1.3.2.tgz#ab759d461670d712425b2dac7573b79575a10502" - integrity sha512-H7qWMs66bztELt2QpOCLYDU9ZM3VZfE0knbRHHLBukH7v9dMkIS5ZwqcGREjWnVt0KNETaBeXxj0FD88TEOGVw== - -"@popperjs/core@^2.9.0": +"@popperjs/core@2.11.6", "@popperjs/core@^2.9.0": version "2.11.6" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== @@ -39,10 +34,10 @@ asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -backbone@^1.3.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" - integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== +backbone@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.1.tgz#099a78184bc07b034048a8332229c2ccca1e3e62" + integrity sha512-ADy1ztN074YkWbHi8ojJVFe3vAanO/lrzMGZWUClIP7oDD/Pjy2vrASraUP+2EVCfIiTtCW4FChVow01XneivA== dependencies: underscore ">=1.8.3" @@ -59,10 +54,15 @@ create-react-class@^15.7.0: loose-envify "^1.3.1" object-assign "^4.1.1" -css.escape@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.0.tgz#95984d7887ce4ca90684e813966f42d1ef87ecea" - integrity sha1-lZhNeIfOTKkGhOgTlm9C0e+H7Oo= +css.escape@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +dompurify@2.4.5: + version "2.4.5" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.5.tgz#0e89a27601f0bad978f9a924e7a05d5d2cccdd87" + integrity sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA== encoding@^0.1.11: version "0.1.13" @@ -76,7 +76,7 @@ event-lite@^0.1.1: resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.2.tgz#838a3e0fdddef8cc90f128006c8e55a4e4e4c11b" integrity sha512-HnSYx1BsJ87/p6swwzv+2v6B4X+uxUteoDfRxsAb1S1BePzQqOLevVmkdA15GHJVd9A9Ok6wygUR18Hu0YeV9g== -fancy-file-input@~2.0.4: +fancy-file-input@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/fancy-file-input/-/fancy-file-input-2.0.4.tgz#698c216482e07649a827681c4db3054fddc9a32b" integrity sha512-l+J0WwDl4nM/zMJ/C8qleYnXMUJKsLng7c5uWH/miAiHoTvPDtEoLW1tmVO6Cy2O8i/1VfA+2YOwg/Q3+kgO6w== @@ -141,7 +141,7 @@ isomorphic-fetch@^2.1.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -jquery-ui@^1.12.1: +jquery-ui@1.13.2: version "1.13.2" resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.2.tgz#de03580ae6604773602f8d786ad1abfb75232034" integrity sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q== @@ -153,10 +153,10 @@ jquery-ui@^1.12.1: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== -jquery@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02" - integrity sha1-LInWiJterFIqfuoywUUhVZxsvwI= +jquery@^3.0.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612" + integrity sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ== "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" @@ -230,11 +230,6 @@ plotly.js-dist@^1.52.3: resolved "https://registry.yarnpkg.com/plotly.js-dist/-/plotly.js-dist-1.52.3.tgz#4c16c6da6adab6cdba169087b5005bdddbf10834" integrity sha512-kpuNwveRk6M/5cCW1ZgJTbMLD0bRZhJlLK0cUHVkTsP/PWKCVJqO3QiiqrypFGE/xEhWfHCY+YKAKjMmEbo4Gw== -popper.js@^1.14.5: - version "1.16.1" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" - integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== - promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -335,7 +330,12 @@ umbrellajs@^3.1.0: resolved "https://registry.yarnpkg.com/umbrellajs/-/umbrellajs-3.1.0.tgz#a4e6f0f6381f9d93110b5eee962e0e0864b10bd0" integrity sha512-3qichMg1Q6EetLweBAT0L55O2W6CJe9qyiSt1RBnf+bcOqwJ4R7e2PDcoIUrCsg+uRo3DXOvurWdklBu0ia7fg== -underscore@>=1.8.3, underscore@^1.9.1: +underscore@1.13.6: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + +underscore@>=1.8.3: version "1.13.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== diff --git a/install/sqls/00037-agent-card-profilefix.sql b/install/sqls/00037-agent-card-profilefix.sql new file mode 100644 index 000000000..e7ebc230b --- /dev/null +++ b/install/sqls/00037-agent-card-profilefix.sql @@ -0,0 +1,2 @@ +ALTER TABLE `support_names` + ADD COLUMN `id` bigint(20) NOT NULL AUTO_INCREMENT UNIQUE FIRST; diff --git a/locales/by.strings b/locales/by.strings index 52297afdb..74c59cbfa 100644 --- a/locales/by.strings +++ b/locales/by.strings @@ -8,6 +8,7 @@ "home" = "Галоўная"; "welcome" = "Сардэчна запрашаем"; +"to_top" = "Уверх"; /* Login */ @@ -42,9 +43,20 @@ "all_title" = "Усе"; "information" = "Інфармацыя"; "status" = "Статус"; +"no_information_provided" = "Няма інфармацыі."; +"deceased_person" = "Старонка памерлага чалавека"; +"none" = "адсутнічае"; +"desc_none" = "няма апісання"; +"send" = "Адправіць"; +"years_zero" = "0 гадоў"; +"years_one" = "1 год"; +"years_few" = "$1 год"; +"years_many" = "$1 год"; +"years_other" = "$1 год"; +"show_my_birthday" = "Паказаць дату нараджэння"; +"show_only_month_and_day" = "Паказваць толькі дзень і месяц"; "relationship" = "Сямейнае становішча"; - "relationship_0" = "Не выбрана"; "relationship_1" = "Ня женаты"; "relationship_2" = "Сустракаюся"; @@ -308,7 +320,13 @@ "round_avatars" = "Круглы аватар"; "search_for_groups" = "Пошук груп"; -"search_for_people" = "Пошук людзей"; +"search_for_users" = "Шукаць карыстальнікаў"; +"search_for_posts" = "Пошук пастоў"; +"search_for_comments" = "Пошук каментарыяў"; +"search_for_videos" = "Шукаць відэа"; +"search_for_apps" = "Шукаць праграмы"; +"search_for_notes" = "Шукаць нататкі"; +"search_for_audios" = "Пошук музыкі"; "search_button" = "Знайсці"; "privacy_setting_access_page" = "Каму ў інтэрнэце відаць маю старонку"; @@ -495,3 +513,50 @@ "cookies_popup_content" = "Усе хлопчыкi любяць пэчыва, таму гэты вэб-сайт выкарыстоўвае Cookies для таго, каб ідэнтыфікаваць вашу сесію і нічога болей. Звяртайцеся да нашай палiтыкi канфiдэнцыальнастi для палучэння поўнай iнфармацыi."; "cookies_popup_agree" = "Прынiмаю"; + +/* Пошук */ + +"s_people" = "Людзі"; +"s_groups" = "Супольнасці"; +"s_events" = "Падзеі"; +"s_apps" = "Прылады"; +"s_questions" = "Пытанні"; +"s_notes" = "Нататкі"; +"s_themes" = "Тэмы"; +"s_posts" = "Пасты"; +"s_comments" = "Каментарыі"; +"s_videos" = "Відэа"; +"s_audios" = "Музыка"; +"s_by_people" = "па людзям"; +"s_by_groups" = "па суполках"; +"s_by_posts" = "па паведамленнях"; +"s_by_comments" = "па каментарыях"; +"s_by_videos" = "па відэа"; +"s_by_apps" = "па прыладам"; +"s_by_audios" = "па музыцы"; + +"s_order_by" = "Сартаваць па..."; + +"s_order_by_id" = "ID"; +"s_order_by_name" = "Па імені"; +"s_order_by_random" = "Выпадкова"; +"s_order_by_rating" = "Па рэйтынгу"; +"s_order_invert" = "Інвертаваць"; + +"s_by_date" = "Па даце"; +"s_registered_before" = "Зарэгістравана да"; +"s_registered_after" = "Зарэгістравана пасля"; +"s_date_before" = "Да"; +"s_date_after" = "Пасля"; + +"s_main" = "Асноўны"; + +"s_now_on_site" = "зараз на сайце"; +"s_with_photo" = "з фота"; +"s_only_in_names" = "толькі ў імёнах"; + +"s_any" = "любы"; +"reset" = "Ачысціць"; + +"closed_group_post" = "Гэта паведамленне з прыватнай групы"; +"deleted_target_comment" = "Гэты каментарый належыць да выдаленага поста"; diff --git a/locales/by_lat.strings b/locales/by_lat.strings index d0db1264c..55e84cd3b 100644 --- a/locales/by_lat.strings +++ b/locales/by_lat.strings @@ -9,6 +9,7 @@ "home" = "Hałoŭnaja"; "welcome" = "Sardečna zaprašajem"; +"to_top" = "Uvierch"; /* Login */ @@ -44,6 +45,17 @@ "information" = "Infarmacyja"; "status" = "Status"; "no_information_provided" = "Niama infarmacyi."; +"deceased_person" = "Staronka pamierlaha čalavieka"; +"none" = "adsutničaje"; +"desc_none" = "niama apisannia"; +"send" = "Adpravić"; +"years_zero" = "0 hadoŭ"; +"years_one" = "1 hod"; +"years_few" = "$1 hod"; +"years_many" = "$1 hod"; +"years_other" = "$1 hod"; +"show_my_birthday" = "Pakazać datu naradžennia"; +"show_only_month_and_day" = "Pakazać toĺki dzień i miesiac"; "relationship" = "Siamiejnaje stanovišča"; @@ -295,7 +307,13 @@ "round_avatars" = "Kruhły avatar"; "search_for_groups" = "Pošuk hurtoŭ"; -"search_for_people" = "Pošuk ludziaŭ"; +"search_for_users" = "Šukać karystaĺnikaŭ"; +"search_for_posts" = "Pošuk pastoŭ"; +"search_for_comments" = "Pošuk kamentaryjaŭ"; +"search_for_videos" = "Šukać videa"; +"search_for_apps" = "Šukać prahramy"; +"search_for_notes" = "Šukać natatki"; +"search_for_audios" = "Pošuk muzyki"; "search_button" = "Znajści"; "privacy_setting_access_page" = "Kamu ŭ interniecie vidać maju staronku"; @@ -455,3 +473,50 @@ "paginator_back" = "Nazad"; "paginator_page" = "Staronka $1"; "paginator_next" = "Dalej"; + +/* Пошук */ + +"s_people" = "Liudzi"; +"s_groups" = "Supolnasci"; +"s_events" = "Padziei"; +"s_apps" = "Prylady"; +"s_questions" = "Pytanni"; +"s_notes" = "Natatki"; +"s_themes" = "Temy"; +"s_posts" = "Pasty"; +"s_comments" = "Kamentaryi"; +"s_videos" = "Videa"; +"s_audios" = "Muzyka"; +"s_by_people" = "pa liudziam"; +"s_by_groups" = "pa supolkach"; +"s_by_posts" = "pa paviedamlienniach"; +"s_by_comments" = "pa kamentaryjach"; +"s_by_videos" = "pa videa"; +"s_by_apps" = "pa pryladam"; +"s_by_audios" = "pa muzycy"; + +"s_order_by" = "Sartavać pa..."; + +"s_order_by_id" = "ID"; +"s_order_by_name" = "Imieni"; +"s_order_by_random" = "Vypadkova"; +"s_order_by_rating" = "Pa rejtynhu"; +"s_order_invert" = "Invertavać"; + +"s_by_date" = "Pa dacie"; +"s_registered_before" = "Zarehistravana da"; +"s_registered_after" = "Zarehistravana paslia"; +"s_date_before" = "Da"; +"s_date_after" = "Paslia"; + +"s_main" = "Asnoŭny"; + +"s_now_on_site" = "zaraz na sajcie"; +"s_with_photo" = "z fota"; +"s_only_in_names" = "toĺki ŭ imionach"; + +"s_any" = "liuby"; +"reset" = "Ačyscić"; + +"closed_group_post" = "Heta paviedamliennie z pryvatnaj hrupy"; +"deleted_target_comment" = "Heta paviedamliennie z pryvatnaj hrupy"; diff --git a/locales/en.strings b/locales/en.strings index 63cdeb79e..8647546e9 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -500,6 +500,7 @@ "search_for_videos" = "Search for videos"; "search_for_apps" = "Search for apps"; "search_for_notes" = "Search for notes"; +"search_for_audios" = "Search for music"; "search_button" = "Find"; "search_placeholder" = "Start typing any name, title or word"; "results_zero" = "No results"; @@ -1505,6 +1506,8 @@ "s_order_invert" = "Invert"; "s_by_date" = "By date"; +"s_registered_before" = "Registered before"; +"s_registered_after" = "Registered after"; "s_date_before" = "Before"; "s_date_after" = "After"; diff --git a/locales/ru.strings b/locales/ru.strings index 1b6371b81..a44135e04 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -476,6 +476,7 @@ "search_for_videos" = "Поиск видео"; "search_for_apps" = "Поиск приложений"; "search_for_notes" = "Поиск записок"; +"search_for_audios" = "Поиск музыки"; "search_button" = "Найти"; "search_placeholder" = "Начните вводить любое имя, название или слово"; "results_zero" = "Ни одного результата"; @@ -1401,6 +1402,8 @@ "s_order_invert" = "Инвертировать"; "s_by_date" = "По дате"; +"s_registered_before" = "Зарегистрирован до"; +"s_registered_after" = "Зарегистрирован после"; "s_date_before" = "До"; "s_date_after" = "После"; diff --git a/locales/ru_old.strings b/locales/ru_old.strings index c75889db7..d0917f651 100644 --- a/locales/ru_old.strings +++ b/locales/ru_old.strings @@ -414,6 +414,7 @@ "search_for_videos" = "Поискъ синематографовъ"; "search_for_apps" = "Поискъ забав"; "search_for_notes" = "Поискъ запiсокъ"; +"search_for_audios" = "Поиск музыкъ"; "search_button" = "Найти"; "results_zero" = "Ни одного результата"; "results_one" = "Одинъ результатъ"; @@ -757,6 +758,8 @@ "s_order_invert" = "Отразiть"; "s_by_date" = "По датѣ"; +"s_registered_before" = "Зарѣгистрированъ до"; +"s_registered_after" = "Зарѣгистрированъ после"; "s_date_before" = "До"; "s_date_after" = "После"; diff --git a/locales/ru_sov.strings b/locales/ru_sov.strings index b87cd6f07..e82f36b45 100644 --- a/locales/ru_sov.strings +++ b/locales/ru_sov.strings @@ -433,6 +433,7 @@ "search_for_videos" = "Розыск кинолент"; "search_for_apps" = "Розыск приложений"; "search_for_notes" = "Розыск записок"; +"search_for_audios" = "Розыск аудио"; "search_button" = "Найти"; "search_placeholder" = "Начните вводить любое имя, название или слово"; @@ -962,6 +963,8 @@ "s_order_invert" = "Отразить"; "s_by_date" = "По дате"; +"s_registered_before" = "Зарегистрирован до"; +"s_registered_after" = "Зарегистрирован после"; "s_date_before" = "До"; "s_date_after" = "После"; diff --git a/locales/uk.strings b/locales/uk.strings index 6db81560f..53f8c9c63 100644 --- a/locales/uk.strings +++ b/locales/uk.strings @@ -47,7 +47,7 @@ "register_referer_meta_title" = "$1 запрошує вас у $2!"; "register_referer_meta_desc" = "Приєднуйтесь до $1 та багатьох інших користувачів у $2!"; "registration_welcome_1" = "- універсальний засіб пошуку колег, засноване на структурі ВКонтакте."; -"registration_welcome_2" = "Ми бажаємо, щоб друзі, однокурсники, однокласники, сусіди і колеги завжди могли бути в контакті."; +"registration_welcome_2" = "Ми бажаємо, щоб друзі, однокурсники, однокласники, сусіди та колеги завжди могли бути в контакті."; "users" = "Користувачі"; "other_fields" = "Інше"; @@ -82,6 +82,7 @@ "no_information_provided" = "Інформація відсутня."; "deceased_person" = "Сторінка покійної людини"; "none" = "відсутній"; +"desc_none" = "немає опису"; "send" = "Відправити"; "years_zero" = "0 років"; "years_one" = "1 рік"; @@ -454,7 +455,13 @@ "round avatars" = "Круглі"; "apply_style_for_this_device" = "Застосувати стиль лише для цього пристрою"; "search_for_groups" = "Пошук груп"; -"search_for_people" = "Пошук людей"; +"search_for_users" = "Пошук користувачів"; +"search_for_posts" = "Пошук дописів"; +"search_for_comments" = "Пошук коментарів"; +"search_for_videos" = "Пошук відео"; +"search_for_apps" = "Пошук додатків"; +"search_for_notes" = "Пошук нотаток"; +"search_for_audios" = "Пошук музики"; "search_button" = "Знайти"; "search_placeholder" = "Почніть вводити будь-яке ім'я, назву чи слово"; "results_zero" = "Жодного результату"; @@ -839,7 +846,7 @@ "banned_2" = "Привід: $1."; "banned_perm" = "На цей раз, Ви заблоковані назавжди."; "banned_until_time" = "На цей раз, Ви заблоковані до $1"; -"banned_3" = "Ви все ще можете написати в службу підтримки, якщо вважаєте, що сталася помилка або вийти."; +"banned_3" = "Ви все ще можете написати в службу підтримки, якщо вважаєте що сталася помилка або вийти."; "banned_unban_myself" = "Розблокувати сторінку"; "banned_unban_title" = "Ваш обліковий запис розблокований"; "banned_unban_description" = "Намагайтеся, більше не порушувати правила."; @@ -1167,8 +1174,8 @@ "global_maintenance" = "Технічні роботи"; "section_maintenance" = "Розділ недоступний"; -"undergoing_global_maintenance" = "На жаль, зараз інстанція закрита на технічні роботи. Ми вже працюємо над усуненням неполадок. Будь ласка, спробуйте зайти пізніше."; -"undergoing_section_maintenance" = "На жаль, розділ $1 тимчасово недоступний. Ми вже працюємо над усуненням неполадок. Будь ласка, спробуйте зайти пізніше."; +"undergoing_global_maintenance" = "Інстанція закрита на технічні роботи. Будь ласка, спробуйте зайти пізніше."; +"undergoing_section_maintenance" = "Розділ $1 тимчасово недоступний. Будь ласка, спробуйте зайти пізніше."; "topics" = "Теми"; /* Polls */ @@ -1260,11 +1267,11 @@ "tour_section_2_text_2_2" = "Ви маєте змогу закрити доступ до своєї сторінки від пошукових систем і незареєстрованих користувачів."; "tour_section_2_text_2_3" = "Пам'ятайте: у майбутньому налаштування приватності будуть розширюватися."; "tour_section_2_title_3" = "Персональна адреса сторінки"; -"tour_section_2_text_3_1" = "Після реєстрації сторінки, вам видається персональний ID типу: /id12345"; +"tour_section_2_text_3_1" = "Після реєстрації сторінки, вам видається персональний ID типа: /id12345"; "tour_section_2_text_3_2" = "Стандартний ID, який був отриманий після реєстрації, змінити не можна!"; "tour_section_2_text_3_3" = "Однак, в налаштуваннях своєї сторінки ви зможете прив'язати свою персональну адресу і цю адресу можна буде змінити в будь-який час"; "tour_section_2_text_3_4" = "Порада: Можна займати будь-яку вільну адресу, довжина якої не менша за 5 символів."; -"tour_section_2_bottom_text_2" = "Підтримується встановлення будь-якої короткої адреси з латинських маленьких літер; адреса може містити цифри (однак, не на початку), крапки та нижні підкреслення (не на початку або наприкінці)"; +"tour_section_2_bottom_text_2" = "Коротка адреса може складатися лише з латинських маленьких літер (a-z), цифр, крапок та нижніх підкреслень (але не напочатку чи кінці)"; "tour_section_2_title_4" = "Стіна"; "tour_section_3_title_1" = "Діліться своїми фотографіями"; @@ -1288,7 +1295,7 @@ "tour_section_5_text_3" = "Окрім завантаження відео напряму, сайт підтримує і вбудовані відео через YouTube"; "tour_section_6_title_1" = "Аудіозаписи"; -"tour_section_6_text_1" = "!!! АУДІОЗАПИСІВ НЕМАЄ, ЧЕКАЙТЕ ІНФОРМАЦІЇ ВІД ГЕН.ШТАБУ !!!"; +"tour_section_6_text_1" = "Аудіозаписи в розробці, стежте за новинами"; "tour_section_7_title_1" = "Слідкуйте за тим, що пишуть ваші друзі"; "tour_section_7_text_1" = "Розділ "Мої Новини" поділяється на два типи: локальна стрічка та глобальна стрічка новин"; @@ -1333,14 +1340,14 @@ "tour_section_12_text_3_1" = "За потреби, ви можете приховати непотрібні розділи сайту"; "tour_section_12_text_3_2" = "Нагадування: Розділи першої необхідності (Моя Сторінка; Мої Друзі; Мої Відповіді; Мої Налаштування) приховати не можна"; "tour_section_12_title_4" = "Вид постів"; -"tour_section_12_text_4_1" = "Якщо набрид старий дизайн стіни, який був у колись популярному оригінальному ВКонтактє.сру, то ви завжди можете змінити вигляд постів на Мікроблог"; +"tour_section_12_text_4_1" = "Якщо набрид старий дизайн стіни, який був у колись популярному оригінальному ВКонтакті, то ви завжди можете змінити вигляд постів на Мікроблог"; "tour_section_12_text_4_2" = "Вид постів можна змінювати між двома варіантами в будь-який час"; "tour_section_12_text_4_3" = "Зверніть увагу, що якщо обрано старий вид відображення постів, то останні коментарі довантажуватися не будуть"; "tour_section_12_bottom_text_1" = "Сторінка встановлення фону"; "tour_section_12_bottom_text_2" = "Приклади сторінок зі встановленим фоном"; "tour_section_12_bottom_text_3" = "За допомогою цієї можливості ви можете додати своєму профілю більше індивідуальності"; "tour_section_12_bottom_text_4" = "Старий вигляд постів"; -"tour_section_12_bottom_text_5" = "Мікроблок"; +"tour_section_12_bottom_text_5" = "Мікроблог"; "tour_section_13_title_1" = "Ваучер"; "tour_section_13_text_1" = "Ваучер в OpenVK це щось на кшталт промокоду на додавання будь-якої валюти (відсотки рейтингу, голосів тощо)"; @@ -1349,9 +1356,10 @@ "tour_section_13_text_4" = "Пам'ятайте: Усі ваучери мають обмежений термін активації"; "tour_section_13_bottom_text_1" = "Ваучери складаються з 24 цифр та літер"; "tour_section_13_bottom_text_2" = "Активація пройшла вдало (наприклад, нам зарахували 100 голосів)"; -"tour_section_13_bottom_text_3" = "Увага: Після активації ваучера на вашу сторінку, той самий ваучер не можна буде активувати повторно"; +"tour_section_13_bottom_text_3" = "Зверніть увагу: Якщо ви вже активували ваучер на цьому аккаунті, його не можна повторно використати."; "tour_section_14_title_1" = "Мобільна версія"; + "tour_section_14_text_1" = "На даний момент існує мобільна версія сайту, що використовує тему OpenVK Mobile, та мобільний додаток для Android."; "tour_section_14_text_2" = "OpenVK Legacy - це мобільний додаток OpenVK для пристроїв на базі Android із дизайном ВКонтакте 3.0.4 2013 року"; "tour_section_14_text_3" = "Мінімально підтримуваною версією є Android 2.1 Eclair, тобто апарати часів початку 2010-их стануть у пригоді."; @@ -1364,3 +1372,50 @@ "tour_section_14_bottom_text_1" = "Скріншоти застосунку"; "tour_section_14_bottom_text_2" = "На цьому екскурсія сайтом завершена. Якщо ви хочете спробувати наш мобільний застосунок, створити тут свою групу, покликати своїх друзів чи знайти нових, або взагалі просто якось розважитися, то це можна зробити просто зараз, пройшовши невелику реєстрацію"; "tour_section_14_bottom_text_3" = "На цьому екскурсія сайтом завершена."; + +/* Search */ + +"s_people" = "Люди"; +"s_groups" = "Спільноти"; +"s_events" = "Події"; +"s_apps" = "Застосунки"; +"s_questions" = "Запитання"; +"s_notes" = "Нотатки"; +"s_themes" = "Теми"; +"s_posts" = "Дописи"; +"s_comments" = "Коментарі"; +"s_videos" = "Відео"; +"s_audios" = "Музика"; +"s_by_people" = "по людям"; +"s_by_groups" = "по спільнотах"; +"s_by_posts" = "по дописах"; +"s_by_comments" = "по коментарях"; +"s_by_videos" = "по відео"; +"s_by_apps" = "по застосункам"; +"s_by_audios" = "по музиці"; + +"s_order_by" = "Сортувати за..."; + +"s_order_by_id" = "ID"; +"s_order_by_name" = "Назвою"; +"s_order_by_random" = "Випадковістю"; +"s_order_by_rating" = "Рейтингом"; +"s_order_invert" = "Інвертувати"; + +"s_by_date" = "За датою"; +"s_registered_before" = "Зареєстровано до"; +"s_registered_after" = "Зареєстровано після"; +"s_date_before" = "До"; +"s_date_after" = "Після"; + +"s_main" = "Основне"; + +"s_now_on_site" = "зараз на сайті"; +"s_with_photo" = "з фото"; +"s_only_in_names" = "тільки в іменах"; + +"s_any" = "будь-який"; +"reset" = "Очистити"; + +"closed_group_post" = "Цей допис з приватної групи"; +"deleted_target_comment" = "Цей коментар належить до видаленого допису"; diff --git a/themepacks/modern/stylesheet.css b/themepacks/modern/stylesheet.css index 63c2cc5b8..b606205a2 100644 --- a/themepacks/modern/stylesheet.css +++ b/themepacks/modern/stylesheet.css @@ -249,3 +249,95 @@ input[type=checkbox] { width:100%; background-color:#3c3c3c } + +.searchOptions +{ + overflow-y: hidden; + overflow-x:hidden; + width:25.5%; + border-top:1px solid #2B2B2B; + float:right; + scrollbar-width: none; + font-size:12px; + background-color:#F6F6F6; + margin-right: -7px; +} + +.searchBtn +{ + border: none; + background-color: #555555; + color: #ffffff; + margin-left: -3px; + padding-bottom: 2px; + width: 80px; + cursor: pointer; + box-shadow: none; +} + +.searchBtn:active +{ + border: none; + background-color: rgb(77, 77, 77); + color: white; + box-shadow: none; +} + +.searchList #used +{ + margin-left:0px; + color: white; + padding: 2px; + padding-top: 5px; + padding-bottom: 5px; + border: none; + background: #a4a4a4; + margin-bottom: 2px; + padding-left: 5px; + width: 90%; +} + +.searchList #used a +{ + color: white; +} + +.searchHide +{ + padding-right: 5px; +} + +.searchList li:hover +{ + margin-left: 0px; + color: #2B587A !important; + background: #eeeeee; + padding: 2px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 2px; + padding-left: 5px; + width: 91%; +} + +.searchOptionName +{ + cursor: pointer; + background-color: #a4a4a4; + padding-left: 5px; + padding-top: 5px; + padding-bottom: 5px; + width: 90%; + font-weight: 600; + color: #fff; +} + +.searchOptionName img +{ + display: none; +} + +.borderup +{ + border-top: 1px solid #2f2f2f; +}