getUser()->getId(), $group, 0, # this is unused but stays here base64 reasons (X2 doesn't work, so there's dummy value for short) ]; $uploadInfo = pack("vZ10v2P3S", ...$uploadInfo); $uploadInfo = base64_encode($uploadInfo); $uploadHash = hash_hmac("sha3-224", $uploadInfo, $secret); $uploadInfo = rawurlencode($uploadInfo); return ovk_scheme(true) . $_SERVER["HTTP_HOST"] . "/upload/photo/$uploadHash?$uploadInfo"; } private function getImagePath(string $photo, string $hash, ?string& $up = NULL, ?string& $group = NULL): string { $secret = CHANDLER_ROOT_CONF["security"]["secret"]; if(!hash_equals(hash_hmac("sha3-224", $photo, $secret), $hash)) $this->fail(121, "Incorrect hash"); [$up, $image, $group] = explode("|", $photo); $imagePath = __DIR__ . "/../../tmp/api-storage/photos/$up" . "_$image.oct"; if(!file_exists($imagePath)) $this->fail(10, "Invalid image"); return $imagePath; } function getOwnerPhotoUploadServer(int $owner_id = 0): object { $this->requireUser(); if($owner_id < 0) { $club = (new Clubs)->get(abs($owner_id)); if(!$club) $this->fail(0404, "Club not found"); else if(!$club->canBeModifiedBy($this->getUser())) $this->fail(200, "Access: Club can't be 'written' by user"); } return (object) [ "upload_url" => $this->getPhotoUploadUrl("photo", !isset($club) ? 0 : $club->getId()), ]; } function saveOwnerPhoto(string $photo, string $hash): object { $imagePath = $this->getImagePath($photo, $hash, $uploader, $group); if($group == 0) { $user = (new \openvk\Web\Models\Repositories\Users)->get((int) $uploader); $album = (new Albums)->getUserAvatarAlbum($user); } else { $club = (new Clubs)->get((int) $group); $album = (new Albums)->getClubAvatarAlbum($club); } try { $avatar = new Photo; $avatar->setOwner((int) $uploader); $avatar->setDescription("Profile photo"); $avatar->setCreated(time()); $avatar->setFile([ "tmp_name" => $imagePath, "error" => 0, ]); $avatar->save(); $album->addPhoto($avatar); unlink($imagePath); } catch(ImageException | InvalidStateException $e) { unlink($imagePath); $this->fail(129, "Invalid image file"); } return (object) [ "photo_hash" => NULL, "photo_src" => $avatar->getURL(), ]; } function getWallUploadServer(?int $group_id = NULL): object { $this->requireUser(); $album = NULL; if(!is_null($group_id)) { $club = (new Clubs)->get(abs($group_id)); if(!$club) $this->fail(0404, "Club not found"); else if(!$club->canBeModifiedBy($this->getUser())) $this->fail(200, "Access: Club can't be 'written' by user"); } else { $album = (new Albums)->getUserWallAlbum($this->getUser()); } return (object) [ "upload_url" => $this->getPhotoUploadUrl("photo", $group_id ?? 0), "album_id" => $album, "user_id" => $this->getUser()->getId(), ]; } function saveWallPhoto(string $photo, string $hash, int $group_id = 0, ?string $caption = NULL): array { $imagePath = $this->getImagePath($photo, $hash, $uploader, $group); if($group_id != $group) $this->fail(8, "group_id doesn't match"); $album = NULL; if($group_id != 0) { $uploader = (new \openvk\Web\Models\Repositories\Users)->get((int) $uploader); $album = (new Albums)->getUserWallAlbum($uploader); } try { $photo = new Photo; $photo->setOwner((int) $uploader); $photo->setCreated(time()); $photo->setFile([ "tmp_name" => $imagePath, "error" => 0, ]); if (!is_null($caption)) $photo->setDescription($caption); $photo->save(); unlink($imagePath); } catch(ImageException | InvalidStateException $e) { unlink($imagePath); $this->fail(129, "Invalid image file"); } if(!is_null($album)) $album->addPhoto($photo); return [ $photo->toVkApiStruct(), ]; } function getUploadServer(?int $album_id = NULL): object { $this->requireUser(); # Not checking rights to album because save() method will do so anyways return (object) [ "upload_url" => $this->getPhotoUploadUrl("photo", 0, true), "album_id" => $album_id, "user_id" => $this->getUser()->getId(), ]; } function save(string $photos_list, string $hash, int $album_id = 0, ?string $caption = NULL): object { $this->requireUser(); $secret = CHANDLER_ROOT_CONF["security"]["secret"]; if(!hash_equals(hash_hmac("sha3-224", $photos_list, $secret), $hash)) $this->fail(121, "Incorrect hash"); $album = NULL; if($album_id != 0) { $album_ = (new Albums)->get($album_id); if(!$album_) $this->fail(0404, "Invalid album"); else if(!$album_->canBeModifiedBy($this->getUser())) $this->fail(15, "Access: Album can't be 'written' by user"); $album = $album_; } $pList = json_decode($photos_list); $imagePaths = []; foreach($pList as $pDesc) $imagePaths[] = __DIR__ . "/../../tmp/api-storage/photos/$pDesc->keyholder" . "_$pDesc->resource.oct"; $images = []; try { foreach($imagePaths as $imagePath) { $photo = new Photo; $photo->setOwner($this->getUser()->getId()); $photo->setCreated(time()); $photo->setFile([ "tmp_name" => $imagePath, "error" => 0, ]); if (!is_null($caption)) $photo->setDescription($caption); $photo->save(); unlink($imagePath); if(!is_null($album)) $album->addPhoto($photo); $images[] = $photo->toVkApiStruct(); } } catch(ImageException | InvalidStateException $e) { foreach($imagePaths as $imagePath) unlink($imagePath); $this->fail(129, "Invalid image file"); } return (object) [ "count" => sizeof($images), "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; } }