diff --git a/ServiceAPI/Photos.php b/ServiceAPI/Photos.php new file mode 100644 index 00000000..8f3e3c02 --- /dev/null +++ b/ServiceAPI/Photos.php @@ -0,0 +1,65 @@ +user = $user; + $this->photos = new PhotosRepo; + } + + function getPhotos(int $page = 1, int $album = 0, callable $resolve, callable $reject) + { + if($album == 0) { + $photos = $this->photos->getEveryUserPhoto($this->user, $page, 24); + $count = $this->photos->getUserPhotosCount($this->user); + } else { + $album = (new Albums)->get($album); + + if(!$album || $album->isDeleted() || $album->getOwner()->getId() != $this->user->getId()) + $reject(55, "Invalid ."); + + $photos = $album->getPhotos($page, 24); + $count = $album->size(); + } + + $arr = [ + "count" => $count, + "items" => [], + ]; + + foreach($photos as $photo) { + $res = json_decode(json_encode($photo->toVkApiStruct()), true); + + $arr["items"][] = $res; + } + + $resolve($arr); + } + + function getAlbums(callable $resolve, callable $reject) + { + $albumsRepo = (new Albums); + $count = $albumsRepo->getUserAlbumsCount($this->user); + $albums = $albumsRepo->getUserAlbums($this->user, 1, $count); + + $arr = [ + "count" => $count, + "items" => [], + ]; + + foreach($albums as $album) { + $res = ["id" => $album->getId(), "name" => $album->getName()]; + + $arr["items"][] = $res; + } + + $resolve($arr); + } +} diff --git a/Web/Models/Repositories/Photos.php b/Web/Models/Repositories/Photos.php index 88c7e804..0698c914 100644 --- a/Web/Models/Repositories/Photos.php +++ b/Web/Models/Repositories/Photos.php @@ -33,14 +33,26 @@ class Photos return new Photo($photo); } - function getEveryUserPhoto(User $user): \Traversable + function getEveryUserPhoto(User $user, int $page = 1, ?int $perPage = NULL): \Traversable { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; $photos = $this->photos->where([ - "owner" => $user->getId() - ]); + "owner" => $user->getId(), + "deleted" => 0 + ])->order("id DESC"); - foreach($photos as $photo) { + foreach($photos->page($page, $perPage) as $photo) { yield new Photo($photo); } } + + function getUserPhotosCount(User $user) + { + $photos = $this->photos->where([ + "owner" => $user->getId(), + "deleted" => 0 + ]); + + return sizeof($photos); + } } diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php index b68c7d11..e005af86 100644 --- a/Web/Presenters/CommentPresenter.php +++ b/Web/Presenters/CommentPresenter.php @@ -2,7 +2,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post}; use openvk\Web\Models\Entities\Notifications\CommentNotification; -use openvk\Web\Models\Repositories\{Comments, Clubs, Videos}; +use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos}; final class CommentPresenter extends OpenVKPresenter { @@ -54,9 +54,6 @@ final class CommentPresenter extends OpenVKPresenter if ($entity instanceof Post && $entity->getWallOwner()->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden")); - if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) - $this->flashFail("err", tr("error"), tr("video_uploads_disabled")); - $flags = 0; if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity)) $flags |= 0b10000000; @@ -70,18 +67,22 @@ final class CommentPresenter extends OpenVKPresenter } } - # TODO move to trait - try { - $photo = NULL; - if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { - $album = NULL; - if($wall > 0 && $wall === $this->user->id) - $album = (new Albums)->getUserWallAlbum($wallOwner); - - $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album); + $photos = []; + if(!empty($this->postParam("photos"))) { + $un = rtrim($this->postParam("photos"), ","); + $arr = explode(",", $un); + + if(sizeof($arr) < 11) { + foreach($arr as $dat) { + $ids = explode("_", $dat); + $photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]); + + if(!$photo || $photo->isDeleted()) + continue; + + $photos[] = $photo; + } } - } catch(ISE $ex) { - $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_file_too_big")); } $videos = []; @@ -103,7 +104,7 @@ final class CommentPresenter extends OpenVKPresenter } } - if(empty($this->postParam("text")) && !$photo && !$video) + if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1) $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty")); try { @@ -119,8 +120,8 @@ final class CommentPresenter extends OpenVKPresenter $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big")); } - if(!is_null($photo)) - $comment->attach($photo); + foreach($photos as $photo) + $comment->attach($photo); if(sizeof($videos) > 0) foreach($videos as $vid) diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index efcb0179..d7caab01 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -97,14 +97,13 @@ final class InternalAPIPresenter extends OpenVKPresenter } } - function renderGetPhotosFromPost(string $post_id) { + function renderGetPhotosFromPost(int $owner_id, int $post_id) { if($_SERVER["REQUEST_METHOD"] !== "POST") { header("HTTP/1.1 405 Method Not Allowed"); exit("иди нахуй заебал"); } - $id = explode("_", $post_id); - $post = (new Posts)->getPostById(intval($id[0]), intval($id[1])); + $post = (new Posts)->getPostById($owner_id, $post_id); if(is_null($post)) { $this->returnJson([ diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php index bef984b7..0a8b87e4 100644 --- a/Web/Presenters/PhotosPresenter.php +++ b/Web/Presenters/PhotosPresenter.php @@ -222,15 +222,20 @@ final class PhotosPresenter extends OpenVKPresenter { $this->assertUserLoggedIn(); $this->willExecuteWriteAction(true); - - if(is_null($this->queryParam("album"))) - $this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true); - - [$owner, $id] = explode("_", $this->queryParam("album")); - $album = $this->albums->get((int) $id); + + if(is_null($this->queryParam("album"))) { + $album = $this->albums->getUserWallAlbum($this->user->identity); + } else { + [$owner, $id] = explode("_", $this->queryParam("album")); + $album = $this->albums->get((int) $id); + } + if(!$album) $this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true); - if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) + + # Для быстрой загрузки фоток из пикера фотографий нужен альбом, но юзер не может загружать фото + # в системные альбомы, так что так. + if(is_null($this->user) || !is_null($this->queryParam("album")) && !$album->canBeModifiedBy($this->user->identity)) $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), 500, true); if($_SERVER["REQUEST_METHOD"] === "POST") { @@ -261,6 +266,9 @@ final class PhotosPresenter extends OpenVKPresenter $this->flashFail("err", tr("no_photo"), tr("select_file"), 500, true); $photos = []; + if((int)$this->postParam("count") > 10) + $this->flashFail("err", tr("no_photo"), "ты еблан", 500, true); + for($i = 0; $i < $this->postParam("count"); $i++) { try { $photo = new Photo; diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index 4635a6f4..2f9d611d 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -3,7 +3,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Exceptions\TooMuchOptionsException; use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User}; use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification}; -use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments}; +use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos}; use Chandler\Database\DatabaseConnection; use Nette\InvalidStateException as ISE; use Bhaktaraz\RSSGenerator\Item; @@ -249,35 +249,23 @@ final class WallPresenter extends OpenVKPresenter if($this->postParam("force_sign") === "on") $flags |= 0b01000000; - try { - $photos = []; - $video = NULL; - /* if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { - $album = NULL; - if(!$anon && $wall > 0 && $wall === $this->user->id) - $album = (new Albums)->getUserWallAlbum($wallOwner); - - $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album, $anon); - } */ + $photos = []; - $album = NULL; - if(!$anon && $wall > 0 && $wall === $this->user->id) - $album = (new Albums)->getUserWallAlbum($wallOwner); + if(!empty($this->postParam("photos"))) { + $un = rtrim($this->postParam("photos"), ","); + $arr = explode(",", $un); - foreach($_FILES as $fileId => $file) { - bdump([$fileId, $file, $file["error"] !== UPLOAD_ERR_OK, strncmp($fileId, "attachPic", 9) !== 0]); - if($file["error"] !== UPLOAD_ERR_OK || strncmp($fileId, "attachPic", 9) !== 0) - continue; - - $photos[] = Photo::fastMake($this->user->id, $this->postParam("text"), $file, $album, $anon); + if(sizeof($arr) < 11) { + foreach($arr as $dat) { + $ids = explode("_", $dat); + $photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]); + + if(!$photo || $photo->isDeleted()) + continue; + + $photos[] = $photo; + } } - - /*if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) - $video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"], $anon);*/ - } catch(\DomainException $ex) { - $this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted")); - } catch(ISE $ex) { - $this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large")); } try { @@ -324,7 +312,7 @@ final class WallPresenter extends OpenVKPresenter } } - if(empty($this->postParam("text")) && !$photos && sizeof($videos) < 1 && !$poll && !$note) + if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && !$poll && !$note) $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big")); try { diff --git a/Web/Presenters/templates/components/textArea.xml b/Web/Presenters/templates/components/textArea.xml index 8eca39eb..44fb5358 100644 --- a/Web/Presenters/templates/components/textArea.xml +++ b/Web/Presenters/templates/components/textArea.xml @@ -60,7 +60,7 @@ {_comment_as_group} - + @@ -77,7 +77,7 @@ {_attach} - + {_photo} @@ -109,6 +109,7 @@ }); u("#post-buttons{$textAreaId} input[name='videos']")["nodes"].at(0).value = "" + u("#post-buttons{$textAreaId} input[name='photos']")["nodes"].at(0).value = "" {if $graffiti} diff --git a/Web/routes.yml b/Web/routes.yml index d61fbd16..dea8ddd5 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -371,7 +371,7 @@ routes: handler: "About->humansTxt" - url: "/dev" handler: "About->dev" - - url: "/iapi/getPhotosFromPost/{text}" + - url: "/iapi/getPhotosFromPost/{num}_{num}" handler: "InternalAPI->getPhotosFromPost" - url: "/tour" handler: "About->tour" diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index 8ea202a4..67debb82 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -92,8 +92,6 @@ u(".post-like-button").on("click", function(e) { return false; }); -let picCount = 0; - function setupWallPostInputHandlers(id) { /* u("#wall-post-input" + id).on("paste", function(e) { if(e.clipboardData.files.length === 1) { @@ -117,25 +115,26 @@ function setupWallPostInputHandlers(id) { }); u(`#wall-post-input${id}`).on("paste", function(e) { + let xhr = new XMLHttpRequest() + let formdat = new FormData() + let iterator = 0 + for (let i = 0; i < e.clipboardData.files.length; i++) { - console.log(e.clipboardData.files[i]); + if(getMediaCount() >= 10) { + alert('Не больше 10 пикч'); + } + if(e.clipboardData.files[i].type.match('^image/')) { - let blobURL = URL.createObjectURL(e.clipboardData.files[i]); - addPhotoMedia(e.clipboardData.files, blobURL, id); + addPhotoMedia(e.clipboardData.files[i]) } } - }); - - u(`#post-buttons${id} input[name=_pic_attachment]`).on("change", function(e) { - let blobURL = URL.createObjectURL(e.target.files[0]); - addPhotoMedia(e.target.files, blobURL, id); + console.log(formdat) }); function addPhotoMedia(files, preview, id) { if(getMediaCount() >= 10) { alert('Не больше 10 пикч'); } else { - picCount++; u(`#post-buttons${id} .upload`).append(u(`
× @@ -190,7 +189,7 @@ function OpenMiniature(e, photo, post, photo_id) {
- +
@@ -396,6 +395,7 @@ function addNote(textareaId, nid) u("body").removeClass("dimmed"); u(".ovk-diag-cont").remove(); + document.querySelector("html").style.overflowY = "scroll" } async function attachNote(id) @@ -711,3 +711,199 @@ $(document).on("click", "#editPost", (e) => { text.style.display = "block" } }) + +// copypaste from videos picker +$(document).on("click", "#photosAttachments", async (e) => { + let body = ` +
+
+ ${tr("upload_new_photo")}: + + + +
+
+ +
+

${tr("is_x_photos", 0)}

+
+
+ ` + + let form = e.currentTarget.closest("form") + + MessageBox(tr("select_photo"), body, [tr("close")], [Function.noop]); + + document.querySelector(".ovk-diag-body").style.padding = "0" + document.querySelector(".ovk-diag-cont").style.width = "630px" + document.querySelector(".ovk-diag-body").style.height = "335px" + + async function insertPhotos(page, album = 0) { + let insertPlace = document.querySelector(".photosInsert .photosList") + document.querySelector(".photosInsert").insertAdjacentHTML("beforeend", ``) + + let photos; + + if(album == 0) { + photos = await API.Photos.getPhotos(page, 0) + } else { + photos = await API.Photos.getPhotos(page, Number(album)) + } + + document.querySelector(".photosInsert h4").innerHTML = tr("is_x_photos", photos.count) + console.log(photos) + + let pagesCount = Math.ceil(Number(photos.count) / 24) + u("#loader").remove() + + for(const photo of photos.items) { + let isAttached = (form.querySelector("input[name='photos']").value.includes(`${photo.owner_id}_${photo.id},`)) + + insertPlace.insertAdjacentHTML("beforeend", ` +
+ + ... + +
+ `) + } + + if(page < pagesCount) { + insertPlace.insertAdjacentHTML("beforeend", ` +
+ more... +
`) + } + } + + insertPhotos(1) + + let albums = await API.Photos.getAlbums() + + for(const alb of albums.items) { + let sel = document.querySelector(".ovk-diag-body #albumSelect") + + sel.insertAdjacentHTML("beforeend", ``) + } + + $(".photosInsert").on("click", "#showMorePhotos", (e) => { + u(e.currentTarget).remove() + insertPhotos(Number(e.currentTarget.dataset.page)) + }) + + $(".topGrayBlock #albumSelect").on("change", (evv) => { + document.querySelector(".photosInsert .photosList").innerHTML = "" + insertPhotos(1, evv.currentTarget.value) + }) + + function insertAttachment(id) { + let photos = form.querySelector("input[name='photos']") + + if(!photos.value.includes(id + ",")) { + if(photos.value.split(",").length > 10) { + NewNotification(tr("error"), tr("max_attached_photos")) + return false + } + + form.querySelector("input[name='photos']").value += (id + ",") + + console.info(id + " attached") + return true + } else { + form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "") + + console.info(id + " detached") + return false + } + } + + $(".photosList").on("click", ".album-photo", (ev) => { + ev.preventDefault() + + if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) { + u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove() + ev.currentTarget.querySelector("img").style.backgroundColor = "white" + } else { + ev.currentTarget.querySelector("img").style.backgroundColor = "#646464" + let id = ev.currentTarget.dataset.attachmentdata + + u(form.querySelector(`.upload`)).append(u(` +
+ × + +
+ `)); + + u(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}'] .upload-delete`).on("click", () => { + form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "") + u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove() + }) + } + }) + + u("#fastFotosUplod").on("change", (evn) => { + let xhr = new XMLHttpRequest() + xhr.open("POST", "/photos/upload") + + let formdata = new FormData() + let iterator = 0 + + for(const fille of evn.currentTarget.files) { + if(!fille.type.startsWith('image/')) { + continue; + } + + if(fille.size > 5 * 1024 * 1024) { + continue; + } + + if(evn.currentTarget.files.length >= 10) { + NewNotification(tr("error"), tr("max_attached_photos")) + return; + } + + formdata.append("photo_"+iterator, fille) + iterator += 1 + } + + xhr.onloadstart = () => { + evn.currentTarget.parentNode.insertAdjacentHTML("beforeend", ``) + } + + xhr.onload = () => { + let result = JSON.parse(xhr.responseText) + + u("#loader").remove() + if(result.success) { + for(const pht of result.photos) { + let id = pht.owner + "_" + pht.vid + + insertAttachment(id) + + u(form.querySelector(`.upload`)).append(u(` +
+ × + +
+ `)); + + u(`.upload #aP[data-id='${pht.owner + "_" + pht.vid}'] .upload-delete`).on("click", () => { + form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "") + u(form.querySelector(`.upload #aP[data-id='${id}']`)).remove() + }) + } + + u("body").removeClass("dimmed"); + u(".ovk-diag-cont").remove(); + document.querySelector("html").style.overflowY = "scroll" + } + } + + formdata.append("hash", u("meta[name=csrf]").attr("value")) + formdata.append("count", iterator) + + xhr.send(formdata) + }) +}) diff --git a/locales/en.strings b/locales/en.strings index 22cee453..b60facc8 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -412,6 +412,16 @@ "tip" = "Tip"; "tip_ctrl" = "to select multiple photos at once, hold down the Ctrl key when selecting files in Windows or the CMD key in Mac OS."; "album_poster" = "Album poster"; +"select_photo" = "Select photos"; +"upload_new_photo" = "Upload new photo"; + +"is_x_photos_zero" = "Just zero photos."; +"is_x_photos_one" = "Just one photo."; +"is_x_photos_few" = "Just $1 photos."; +"is_x_photos_many" = "Just $1 photos."; +"is_x_photos_other" = "Just $1 photos."; + +"all_photos" = "All photos"; /* Notes */ @@ -697,6 +707,7 @@ "selecting_video" = "Selecting videos"; "upload_new_video" = "Upload new video"; "max_attached_videos" = "Max is 10 videos"; +"max_attached_photos" = "Max is 10 photos"; "no_videos" = "You don't have uploaded videos."; "no_videos_results" = "No results."; diff --git a/locales/ru.strings b/locales/ru.strings index 2dfadb8f..be34d424 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -394,6 +394,16 @@ "tip" = "Подсказка"; "tip_ctrl" = "для того, чтобы выбрать сразу несколько фотографий, удерживайте клавишу Ctrl при выборе файлов в ОС Windows или клавишу CMD в Mac OS."; "album_poster" = "Обложка альбома"; +"select_photo" = "Выберите фотографию"; +"upload_new_photo" = "Загрузить новую фотографию"; + +"is_x_photos_zero" = "Всего ноль фотографий."; +"is_x_photos_one" = "Всего одна фотография."; +"is_x_photos_few" = "Всего $1 фотографии."; +"is_x_photos_many" = "Всего $1 фотографий."; +"is_x_photos_other" = "Всего $1 фотографий."; + +"all_photos" = "Все фотографии"; /* Notes */ @@ -656,6 +666,7 @@ "selecting_video" = "Выбор видеозаписей"; "upload_new_video" = "Загрузить новое видео"; "max_attached_videos" = "Максимум 10 видеозаписей"; +"max_attached_photos" = "Максимум 10 фотографий"; "no_videos" = "У вас нет видео."; "no_videos_results" = "Нет результатов.";