diff --git a/VKAPI/Handlers/Messages.php b/VKAPI/Handlers/Messages.php index b3c4c1b83..df654ccb1 100644 --- a/VKAPI/Handlers/Messages.php +++ b/VKAPI/Handlers/Messages.php @@ -141,7 +141,6 @@ public function send( } elseif (!empty($attachment)) { $attachs = parseAttachments($attachment); - # Работают только фотки, остальное просто не будет отображаться. if (sizeof($attachs) >= 10) { $this->fail(15, "Too many attachments"); } diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php index 1d3364c5b..b435fc691 100644 --- a/Web/Models/Entities/Message.php +++ b/Web/Models/Entities/Message.php @@ -7,7 +7,7 @@ use Chandler\Database\DatabaseConnection; use openvk\Web\Models\Repositories\Clubs; use openvk\Web\Models\Repositories\Users; -use openvk\Web\Models\Entities\Photo; +use openvk\Web\Models\Entities\{Photo, Video, Audio, Note, Document}; use openvk\Web\Models\RowModel; use openvk\Web\Util\DateTime; @@ -131,17 +131,77 @@ public function simplify(): array $attachments[] = [ "type" => "photo", "link" => "/photo" . $attachment->getPrettyId(), + "id" => $attachment->getPrettyId(), "photo" => [ "url" => $attachment->getURL(), "caption" => $attachment->getDescription(), ], ]; + } elseif ($attachment instanceof Video) { + if ($attachment->getType() != 1) { + $videoUrl = $attachment->getURL(); + $embedHtml = null; + } else { + $videoUrl = $attachment->getVideoDriver()->getURL(); + $embedHtml = $attachment->getVideoDriver()->getEmbed("100%"); + } + $attachments[] = [ + "type" => "video", + "link" => "/video" . $attachment->getPrettyId(), + "id" => $attachment->getOwner()->getId() . "_" . $attachment->getVirtualId(), + "video" => [ + "url" => $videoUrl, + "embed_html" => $embedHtml, + "name" => $attachment->getName(), + "length" => $attachment->getLength(), + "formatted_length" => $attachment->getFormattedLength(), + "thumbnail" => $attachment->getThumbnailURL(), + "author" => $attachment->getOwner()->getCanonicalName(), + ], + ]; + } elseif ($attachment instanceof Audio) { + $attachments[] = [ + "type" => "audio", + "link" => "/audio" . $attachment->getPrettyId(), + "audio" => [ + "name" => $attachment->getName(), + "artist" => $attachment->getPerformer(), + ], + ]; + } elseif ($attachment instanceof Note) { + $attachments[] = [ + "type" => "note", + "link" => "/note" . $attachment->getId(), + "id" => $attachment->getId(), + "note" => [ + "name" => $attachment->getName(), + ], + ]; + } elseif ($attachment instanceof Document) { + $previewData = null; + if ($attachment->hasPreview()) { + $previewData = [ + "tiny" => $attachment->getPreview()->getURLBySizeId('tiny'), + "medium" => $attachment->getPreview()->getURLBySizeId('medium'), + ]; + } + + $attachments[] = [ + "type" => "doc", + "link" => "/doc" . $attachment->getPrettyId(), + "id" => $attachment->getPrettyId(), + "document" => [ + "name" => $attachment->getName(), + "ext" => $attachment->getFileExtension(), + "size_str" => readable_filesize($attachment->getFilesize()), + "preview" => $previewData, + "pub_time" => (string) $attachment->getPublicationTime(), + ], + ]; } else { $attachments[] = [ "type" => "unknown", ]; - - # throw new \Exception("Unknown attachment type: " . get_class($attachment)); } } diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index b0ee2e83e..9ef394b7d 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -9,12 +9,15 @@ use Nette\Utils\UnknownImageFileException; use openvk\Web\Models\Entities\Album; use openvk\Web\Models\Repositories\Albums; +use openvk\Web\Models\Entities\Traits\TMessageAttachment; use Chandler\Database\DatabaseConnection as DB; use Nette\InvalidStateException as ISE; use Nette\Utils\Image; class Photo extends Media { + use TMessageAttachment; + protected $tableName = "photos"; protected $fileExtension = "jpeg"; @@ -381,12 +384,27 @@ public function toVkApiStruct(bool $photo_sizes = true, bool $extended = false): return $res; } + public function isMessagePhoto(): bool + { + return (bool) $this->getRecord()->is_message_photo; + } + public function canBeViewedBy(?User $user = null): bool { if ($this->isDeleted() || $this->getOwner()->isDeleted()) { return false; } + if ($this->isMessagePhoto()) { + if (!$user) { + return false; + } + if ($user->getId() === $this->getOwner()->getId()) { + return true; + } + return $this->isViewableByMessageParticipant($user); + } + if (!is_null($this->getAlbum())) { return $this->getAlbum()->canBeViewedBy($user); } else { diff --git a/Web/Models/Entities/Traits/TMessageAttachment.php b/Web/Models/Entities/Traits/TMessageAttachment.php new file mode 100644 index 000000000..fa0f31514 --- /dev/null +++ b/Web/Models/Entities/Traits/TMessageAttachment.php @@ -0,0 +1,45 @@ +getContext(); + $msgIds = []; + + foreach ($db->table("attachments") + ->select("target_id") + ->where("attachable_type", get_class($this)) + ->where("attachable_id", $this->getId()) + ->where("target_type", "openvk\\Web\\Models\\Entities\\Message") as $row) { + $msgIds[] = $row->target_id; + } + + if (empty($msgIds)) { + return false; + } + + return (bool) $db->table("messages") + ->where("id", $msgIds) + ->where("deleted", 0) + ->where( + "(sender_type = ? AND sender_id = ?) OR (recipient_type = ? AND recipient_id = ?)", + "openvk\\Web\\Models\\Entities\\User", + $user->getId(), + "openvk\\Web\\Models\\Entities\\User", + $user->getId() + ) + ->count("*"); + } +} diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index 8dbfb7a16..cb4aadb65 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -7,12 +7,15 @@ use openvk\Web\Util\Shell\Shell; use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException}; use openvk\Web\Models\VideoDrivers\VideoDriver; +use openvk\Web\Models\Entities\Traits\TMessageAttachment; use Nette\InvalidStateException as ISE; define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<"); class Video extends Media { + use TMessageAttachment; + public const TYPE_DIRECT = 0; public const TYPE_EMBED = 1; public const TYPE_UNKNOWN = -1; @@ -339,12 +342,27 @@ public function getPageURL(): string return "/video" . $this->getPrettyId(); } + public function isMessageVideo(): bool + { + return (bool) $this->getRecord()->is_message_video; + } + public function canBeViewedBy(?User $user = null): bool { if ($this->isDeleted() || $this->getOwner()->isDeleted()) { return false; } + if ($this->isMessageVideo()) { + if (!$user) { + return false; + } + if ($user->getId() === $this->getOwner()->getId()) { + return true; + } + return $this->isViewableByMessageParticipant($user); + } + if (get_class($this->getOwner()) == "openvk\\Web\\Models\\Entities\\User") { return $this->getOwner()->canBeViewedBy($user) && $this->getOwner()->getPrivacyPermission('videos.read', $user); } else { diff --git a/Web/Models/Repositories/Photos.php b/Web/Models/Repositories/Photos.php index 4de620257..c4626e52d 100644 --- a/Web/Models/Repositories/Photos.php +++ b/Web/Models/Repositories/Photos.php @@ -52,6 +52,7 @@ public function getEveryUserPhoto(User $user, int $offset = 0, int $limit = 10): "system" => 0, "private" => 0, "anonymous" => 0, + "is_message_photo" => 0, ])->order("id DESC"); foreach ($photos->limit($limit, $offset) as $photo) { @@ -67,6 +68,7 @@ public function getUserPhotosCount(User $user) "system" => 0, "private" => 0, "anonymous" => 0, + "is_message_photo" => 0, ]); return sizeof($photos); diff --git a/Web/Models/Repositories/Videos.php b/Web/Models/Repositories/Videos.php index de8e3d95b..c7ec9e343 100644 --- a/Web/Models/Repositories/Videos.php +++ b/Web/Models/Repositories/Videos.php @@ -45,7 +45,7 @@ public function getByOwnerAndVID(int $owner, int $vId): ?Video public function getByUser(User $user, int $page = 1, ?int $perPage = null): \Traversable { $perPage ??= OPENVK_DEFAULT_PER_PAGE; - foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video) { + foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])->page($page, $perPage)->order("created DESC") as $video) { yield new Video($video); } } @@ -53,20 +53,20 @@ public function getByUser(User $user, int $page = 1, ?int $perPage = null): \Tra public function getByUserLimit(User $user, int $offset = 0, int $limit = 10): \Traversable { $perPage ??= OPENVK_DEFAULT_PER_PAGE; - foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->limit($limit, $offset)->order("created DESC") as $video) { + foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])->limit($limit, $offset)->order("created DESC") as $video) { yield new Video($video); } } public function getUserVideosCount(User $user): int { - return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])); + return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])); } public function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream { $query = "%$query%"; - $result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0); + $result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0)->where("is_message_video", 0); $order_str = 'id'; switch ($order['type']) { @@ -101,7 +101,7 @@ public function find(string $query = "", array $params = [], array $order = ['ty public function getLastVideo(User $user) { - $video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->order("id DESC")->fetch(); + $video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])->order("id DESC")->fetch(); return new Video($video); } diff --git a/Web/Presenters/MessengerPresenter.php b/Web/Presenters/MessengerPresenter.php index 0de178c0b..6017c3a74 100644 --- a/Web/Presenters/MessengerPresenter.php +++ b/Web/Presenters/MessengerPresenter.php @@ -143,7 +143,9 @@ public function renderApiGetMessages(int $sel, int $lastMsg): void $messages = []; $correspondence = new Correspondence($this->user->identity, $correspondent); foreach ($correspondence->getMessages(1, $lastMsg === 0 ? null : $lastMsg, null, 0) as $message) { - $messages[] = $message->simplify(); + $simple = $message->simplify(); + $this->enrichAttachmentsWithHTML($message, $simple); + $messages[] = $simple; } header("Content-Type: application/json"); @@ -166,13 +168,62 @@ public function renderApiWriteMessage(int $sel): void exit(); } + $attachments = []; + if (!empty($this->postParam("attachments"))) { + $attachments_array = array_slice(explode(",", $this->postParam("attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]); + if (sizeof($attachments_array) > 0) { + $attachments = parseAttachments($attachments_array, ['photo', 'video', 'audio', 'note', 'doc']); + } + } + $cor = new Correspondence($this->user->identity, $sel); $msg = new Message(); $msg->setContent($this->postParam("content")); $cor->sendMessage($msg); + foreach ($attachments as $attachment) { + if (!$attachment || $attachment->isDeleted() || !$attachment->canBeViewedBy($this->user->identity)) { + continue; + } + + $msg->attach($attachment); + } + header("HTTP/1.1 202 Accepted"); header("Content-Type: application/json"); - exit(json_encode($msg->simplify())); + $simple = $msg->simplify(); + $this->enrichAttachmentsWithHTML($msg, $simple); + exit(json_encode($simple)); + } + + private function enrichAttachmentsWithHTML(Message $messageObj, array &$simplifiedArray): void + { + $children = iterator_to_array($messageObj->getChildren()); + + foreach ($simplifiedArray['attachments'] as $index => &$attachmentData) { + if (!isset($children[$index])) { + continue; + } + + $originalObj = $children[$index]; + $html = ""; + + if ($attachmentData['type'] === 'audio') { + $html = $this->getTemplatingEngine()->renderToString( + # костыль жоский + dirname(__FILE__) . '/templates/Audio/player.latte', + [ + 'audio' => $originalObj, + 'thisUser' => $this->user->identity, + 'hideButtons' => false, + 'club' => null, + ] + ); + } + + if ($html !== "") { + $attachmentData['html'] = $html; + } + } } } diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php index 63abc53d5..b145ed539 100644 --- a/Web/Presenters/PhotosPresenter.php +++ b/Web/Presenters/PhotosPresenter.php @@ -277,9 +277,11 @@ public function renderUploadPhoto(): void $this->willExecuteWriteAction(true); $upload_context = $this->queryParam("upload_context"); + $isMessageUpload = $upload_context === "messages"; + $album = null; if (is_null($this->queryParam("album"))) { - if ((int) $upload_context == $this->user->id) { + if (!$isMessageUpload && (int) $upload_context == $this->user->id) { $album = $this->albums->getUserWallAlbum($this->user->identity); } } else { @@ -339,6 +341,9 @@ public function renderUploadPhoto(): void $photo->setDescription(""); $photo->setFile($_FILES["photo_" . $i]); $photo->setCreated(time()); + if ($isMessageUpload) { + $photo->setIs_message_photo(1); + } $photo->save(); $photos[] = [ @@ -350,11 +355,10 @@ public function renderUploadPhoto(): void "pretty_id" => $photo->getPrettyId(), ]; } catch (ISE $ex) { - $name = $album->getName(); - $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name.", 500, true); + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию" . ($album ? " в " . $album->getName() : "") . ".", 500, true); } - if ($album != null) { + if (!$isMessageUpload && $album != null) { $album->addPhoto($photo); $album->setEdited(time()); $album->save(); diff --git a/Web/Presenters/VideosPresenter.php b/Web/Presenters/VideosPresenter.php index 8bad65c6f..581be392a 100644 --- a/Web/Presenters/VideosPresenter.php +++ b/Web/Presenters/VideosPresenter.php @@ -56,7 +56,7 @@ public function renderView(int $owner, int $vId): void if (!$video || $video->isDeleted()) { $this->notFound(); } - if (!$user->getPrivacyPermission('videos.read', $this->user->identity ?? null)) { + if (!$video->canBeViewedBy($this->user->identity)) { $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); } @@ -103,6 +103,10 @@ public function renderUpload(): void $video->setUnlisted(true); } + if ($this->queryParam("upload_context") === "messages") { + $video->setIs_message_video(1); + } + $video->save(); if ($is_ajax) { diff --git a/Web/Presenters/templates/Messenger/App.latte b/Web/Presenters/templates/Messenger/App.latte index 9cac5ff5f..21a3d14ad 100644 --- a/Web/Presenters/templates/Messenger/App.latte +++ b/Web/Presenters/templates/Messenger/App.latte @@ -29,10 +29,90 @@
@@ -42,15 +122,63 @@ +