diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php
index 9d64ba20..6ec908ec 100644
--- a/Web/Presenters/PhotosPresenter.php
+++ b/Web/Presenters/PhotosPresenter.php
@@ -221,39 +221,71 @@ final class PhotosPresenter extends OpenVKPresenter
function renderUploadPhoto(): void
{
$this->assertUserLoggedIn();
- $this->willExecuteWriteAction();
+ $this->willExecuteWriteAction(true);
if(is_null($this->queryParam("album")))
- $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED.");
+ $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED.", 500, true);
[$owner, $id] = explode("_", $this->queryParam("album"));
$album = $this->albums->get((int) $id);
if(!$album)
- $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED.");
+ $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED.", 500, true);
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
- $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
+ $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.", 500, true);
if($_SERVER["REQUEST_METHOD"] === "POST") {
- if(!isset($_FILES["blob"]))
- $this->flashFail("err", "Нету фотографии", "Выберите файл.");
-
- try {
- $photo = new Photo;
- $photo->setOwner($this->user->id);
- $photo->setDescription($this->postParam("desc"));
- $photo->setFile($_FILES["blob"]);
- $photo->setCreated(time());
- $photo->save();
- } catch(ISE $ex) {
- $name = $album->getName();
- $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name.");
- }
-
- $album->addPhoto($photo);
- $album->setEdited(time());
- $album->save();
+ if($this->queryParam("act") == "finish") {
+ $result = json_decode($this->postParam("photos"), true);
+
+ foreach($result as $photoId => $description) {
+ $phot = $this->photos->get($photoId);
- $this->redirect("/photo" . $photo->getPrettyId() . "?from=album" . $album->getId());
+ if(!$phot || $phot->isDeleted() || $phot->getOwner()->getId() != $this->user->id)
+ continue;
+
+ $phot->setDescription($description);
+ $phot->save();
+
+ $album = $phot->getAlbum();
+ }
+
+ $this->returnJson(["success" => true,
+ "album" => $album->getId(),
+ "owner" => $album->getOwner() instanceof User ? $album->getOwner()->getId() : $album->getOwner()->getId() * -1]);
+ }
+
+ if(!isset($_FILES))
+ $this->flashFail("err", "Нету фотографии", "Выберите файл.", 500, true);
+
+ $photos = [];
+ for($i = 0; $i < $this->postParam("count"); $i++) {
+ try {
+ $photo = new Photo;
+ $photo->setOwner($this->user->id);
+ $photo->setDescription("");
+ $photo->setFile($_FILES["photo_".$i]);
+ $photo->setCreated(time());
+ $photo->save();
+
+ $photos[] = [
+ "url" => $photo->getURLBySizeId("tiny"),
+ "id" => $photo->getId(),
+ "vid" => $photo->getVirtualId(),
+ "owner" => $photo->getOwner()->getId(),
+ "link" => $photo->getURL()
+ ];
+ } catch(ISE $ex) {
+ $name = $album->getName();
+ $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name.", 500, true);
+ }
+
+ $album->addPhoto($photo);
+ $album->setEdited(time());
+ $album->save();
+ }
+
+ $this->returnJson(["success" => true,
+ "photos" => $photos]);
} else {
$this->template->album = $album;
}
@@ -285,7 +317,7 @@ final class PhotosPresenter extends OpenVKPresenter
function renderDeletePhoto(int $ownerId, int $photoId): void
{
$this->assertUserLoggedIn();
- $this->willExecuteWriteAction();
+ $this->willExecuteWriteAction($_SERVER["REQUEST_METHOD"] === "POST");
$this->assertNoCSRF();
$photo = $this->photos->getByOwnerAndVID($ownerId, $photoId);
@@ -298,6 +330,9 @@ final class PhotosPresenter extends OpenVKPresenter
$photo->isolate();
$photo->delete();
+ if($_SERVER["REQUEST_METHOD"] === "POST")
+ $this->returnJson(["success" => true]);
+
$this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
$this->redirect($redirect);
}
diff --git a/Web/Presenters/templates/Photos/UploadPhoto.xml b/Web/Presenters/templates/Photos/UploadPhoto.xml
index 9876e5b9..e2da33e4 100644
--- a/Web/Presenters/templates/Photos/UploadPhoto.xml
+++ b/Web/Presenters/templates/Photos/UploadPhoto.xml
@@ -12,32 +12,22 @@
{/block}
{block content}
-
+
+
+
+
+
+
+
+
+
+
+{/block}
+
+{block bodyScripts}
+ {script "js/al_photos.js"}
{/block}
diff --git a/Web/static/css/main.css b/Web/static/css/main.css
index 55484f13..2bc4d690 100644
--- a/Web/static/css/main.css
+++ b/Web/static/css/main.css
@@ -2700,4 +2700,19 @@ body.article .floating_sidebar, body.article .page_content {
position: absolute;
right: 22px;
font-size: 12px;
-}
\ No newline at end of file
+}
+
+.uploadedImage img {
+ max-height: 76px;
+ object-fit: cover;
+}
+
+.lagged {
+ filter: opacity(0.5);
+ cursor: progress;
+ user-select: none;
+}
+
+.lagged * {
+ pointer-events: none;
+}
diff --git a/Web/static/js/al_photos.js b/Web/static/js/al_photos.js
new file mode 100644
index 00000000..4331dfd6
--- /dev/null
+++ b/Web/static/js/al_photos.js
@@ -0,0 +1,120 @@
+$(document).on("change", "#uploadButton", (e) => {
+ let iterator = 0
+
+ if(e.currentTarget.files.length > 10) {
+ MessageBox(tr("error"), tr("too_many_pictures"), [tr("ok")], [() => {Function.noop}])
+ return;
+ }
+
+ let photos = new FormData()
+ for(file of e.currentTarget.files) {
+ photos.append("photo_"+iterator, file)
+ iterator += 1
+ }
+
+ photos.append("count", e.currentTarget.files.length)
+ photos.append("hash", u("meta[name=csrf]").attr("value"))
+
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", "/photos/upload?album="+document.getElementById("album").value)
+
+ xhr.onloadstart = () => {
+ document.getElementById("photos").insertAdjacentHTML("beforeend", `
`)
+ }
+
+ xhr.onload = () => {
+ let result = JSON.parse(xhr.responseText)
+
+ if(result.success) {
+ u("#loader").remove()
+ let photosArr = result.photos
+
+ for(photo of photosArr) {
+ let table = document.querySelector("#photos")
+
+ table.insertAdjacentHTML("beforeend", `
+
+
+
+ 
+
+ ${tr("delete")}
+ |
+
+
+ |
+
+ `)
+ }
+
+ document.getElementById("endUploading").style.display = "block"
+ } else {
+ u("#loader").remove()
+ MessageBox(tr("error"), result.flash.message ?? tr("error_uploading_photo"), [tr("ok")], [() => {Function.noop}])
+ }
+ }
+
+ xhr.send(photos)
+})
+
+$(document).on("click", "#endUploading", (e) => {
+ let table = document.querySelector("#photos")
+ let data = new FormData()
+ let arr = new Map();
+ for(el of table.querySelectorAll("tr#photo")) {
+ arr.set(el.dataset.id, el.querySelector("textarea").value)
+ }
+
+ data.append("photos", JSON.stringify(Object.fromEntries(arr)))
+ data.append("hash", u("meta[name=csrf]").attr("value"))
+
+ let xhr = new XMLHttpRequest()
+ // в самом вк на каждое изменение описания отправляется свой запрос, но тут мы экономим запросы
+ xhr.open("POST", "/photos/upload?act=finish&album="+document.getElementById("album").value)
+
+ xhr.onloadstart = () => {
+ e.currentTarget.setAttribute("disabled", "disabled")
+ }
+
+ xhr.onerror = () => {
+ MessageBox(tr("error"), tr("error_uploading_photo"), [tr("ok")], [() => {Function.noop}])
+ }
+
+ xhr.onload = () => {
+ let result = JSON.parse(xhr.responseText)
+
+ e.currentTarget.removeAttribute("disabled")
+ document.querySelector(".page_content tbody").innerHTML = ""
+ document.getElementById("endUploading").style.display = "none"
+
+ NewNotification(tr("photos_successfully_uploaded"), tr("click_to_go_to_album"), null, () => {window.location.assign(`/album${result.owner}_${result.album}`)})
+ }
+
+ xhr.send(data)
+})
+
+$(document).on("click", "#deletePhoto", (e) => {
+ let data = new FormData()
+ data.append("hash", u("meta[name=csrf]").attr("value"))
+
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", `/photo${e.currentTarget.dataset.owner}_${e.currentTarget.dataset.id}/delete`)
+
+ xhr.onloadstart = () => {
+ e.currentTarget.closest("tr#photo").classList.add("lagged")
+ }
+
+ xhr.onerror = () => {
+ MessageBox(tr("error"), tr("unknown_error"), [tr("ok")], [() => {Function.noop}])
+ }
+
+ xhr.onload = () => {
+ u(e.currentTarget.closest("tr#photo")).remove()
+
+ if(document.querySelectorAll("tr#photo").length < 1) {
+ document.getElementById("endUploading").style.display = "none"
+ }
+ }
+
+ xhr.send(data)
+})
diff --git a/locales/en.strings b/locales/en.strings
index 487d2156..7c422af6 100644
--- a/locales/en.strings
+++ b/locales/en.strings
@@ -381,6 +381,13 @@
"upd_f" = "updated her profile picture";
"upd_g" = "updated group's picture";
+"upload_picts" = "Upload photos";
+"end_uploading" = "Finish uploading";
+"photos_successfully_uploaded" = "Photos successfully uploaded";
+"click_to_go_to_album" = "Click here to go to album.";
+"error_uploading_photo" = "Error when uploading photo";
+"too_many_pictures" = "No more than 10 pictures";
+
/* Notes */
"notes" = "Notes";
@@ -1066,6 +1073,7 @@
"error_data_too_big" = "Attribute '$1' must be at most $2 $3 long";
"forbidden" = "Access error";
+"unknown_error" = "Unknown error";
"forbidden_comment" = "This user's privacy settings do not allow you to look at his page.";
"changes_saved" = "Changes saved";
diff --git a/locales/ru.strings b/locales/ru.strings
index dc101e2b..404cfe9e 100644
--- a/locales/ru.strings
+++ b/locales/ru.strings
@@ -364,6 +364,13 @@
"upd_f" = "обновила фотографию на своей странице";
"upd_g" = "обновило фотографию группы";
+"upload_picts" = "Загрузить фотографии";
+"end_uploading" = "Завершить загрузку";
+"photos_successfully_uploaded" = "Фотографии успешно загружены";
+"click_to_go_to_album" = "Нажмите, чтобы перейти к альбому.";
+"error_uploading_photo" = "Не удалось загрузить фотографию";
+"too_many_pictures" = "Не больше 10 фотографий";
+
/* Notes */
"notes" = "Заметки";
@@ -982,6 +989,7 @@
"error_repost_fail" = "Не удалось поделиться записью";
"error_data_too_big" = "Аттрибут '$1' не может быть длиннее $2 $3";
"forbidden" = "Ошибка доступа";
+"unknown_error" = "Неизвестная ошибка";
"forbidden_comment" = "Настройки приватности этого пользователя не разрешают вам смотреть на его страницу.";
"changes_saved" = "Изменения сохранены";
"changes_saved_comment" = "Новые данные появятся на вашей странице";