100) $ids = array_slice($ids, 0, 100); // Only take max. 100 items $ids_in = prepare_in($ids); if (!empty($ids_in)) { $sql->modify('chapter_mark_unread', "DELETE FROM mangadex_chapter_views WHERE user_id = ? AND chapter_id IN ($ids_in)", array_merge([$user->user_id], $ids)); $memcached->delete("user_{$user->user_id}_read_chapters"); } $result = 1; break; case "chapter_mark_read": $ids = array_map(function ($e) {return (int)$e;}, explode(',', $_GET["id"])); if (count($ids) > 100) $ids = array_slice($ids, 0, 100); // Only take max. 100 items $q = "INSERT IGNORE INTO mangadex_chapter_views (user_id, chapter_id) VALUES "; $values = []; $binds = []; foreach ($ids AS $id) { $values[]= "(?, ?)"; $binds[] = $user->user_id; $binds[] = $id; } $q .= implode(',', $values); $sql->modify('chapter_mark_read', $q, $binds); $memcached->delete("user_{$user->user_id}_read_chapters"); $result = 1; break; case "chapter_purge": $id = prepare_numeric($_GET["id"]); $chapter = new Chapter($id); if (count(get_object_vars($chapter))) { if (validate_level($user, 'gmod')) { $sql->modify('chapter_purge', " UPDATE mangadex_chapters SET manga_id = 47 WHERE chapter_id = ? LIMIT 1 ", [$id]); $memcached->delete("chapter_$id"); // Add undelete change to manga history $sql->modify('manga_history', 'INSERT INTO mangadex_manga_history (`user_id`, `manga_id`, `timestamp`, `action`, `changes`) VALUES (?,?,?,?,?)', [$user->user_id ?? 1, $chapter->manga_id, time(), 'chapter_purge', json_encode([sprintf('Purge Chapter %1$d ', $chapter->chapter_id)])]); $details = $id; } else { $details = "You can't purge Chapter #$id."; print display_alert("danger", "Failed", $details); // fail } } else { $details = "Chapter #$id does not exist."; print display_alert("danger", "Failed", $details); // fail } $result = (!is_numeric($details)) ? 0 : 1; break; case "chapter_unavailable": $id = prepare_numeric($_GET["id"]); $chapter = new Chapter($id); if (count(get_object_vars($chapter))) { if (validate_level($user, 'gmod')) { $sql->modify('chapter_undelete', " UPDATE mangadex_chapters SET available = 0, chapter_deleted = 0 WHERE chapter_id = ? LIMIT 1 ", [$id]); $sql->modify('chapter_undelete', " UPDATE mangadex_users SET user_uploads = user_uploads + 1 WHERE user_id = ? LIMIT 1 ", [$chapter->user_id]); $memcached->delete("chapter_$id"); $memcached->delete("user_$chapter->user_id"); // Add undelete change to manga history $sql->modify('manga_history', 'INSERT INTO mangadex_manga_history (`user_id`, `manga_id`, `timestamp`, `action`, `changes`) VALUES (?,?,?,?,?)', [$user->user_id ?? 1, $chapter->manga_id, time(), 'chapter_unavailable', json_encode([sprintf('Unavailable Chapter %1$d ', $chapter->chapter_id)])]); $details = $id; } else { $details = "You can't make Chapter #$id unavailable."; print display_alert("danger", "Failed", $details); // fail } } else { $details = "Chapter #$id does not exist."; print display_alert("danger", "Failed", $details); // fail } $result = (!is_numeric($details)) ? 0 : 1; break; case "chapter_undelete": $id = prepare_numeric($_GET["id"]); $chapter = new Chapter($id); if (count(get_object_vars($chapter))) { if (validate_level($user, 'gmod')) { $sql->modify('chapter_undelete', " UPDATE mangadex_chapters SET chapter_deleted = 0 WHERE chapter_id = ? LIMIT 1 ", [$id]); $sql->modify('chapter_undelete', " UPDATE mangadex_users SET user_uploads = user_uploads + 1 WHERE user_id = ? LIMIT 1 ", [$chapter->user_id]); $is_delayed = $chapter->upload_timestamp > time(); // Upload date is in the future if ($is_delayed) { // We undeleted a chapter that is still due to be released in the future. Make sure the chapter exists in the delayed_chapters table $sql->modify('chapter_delayed', 'INSERT INTO mangadex_delayed_chapters (`chapter_id`, `manga_id`, `upload_timestamp`) VALUES (?,?,?) ON DUPLICATE KEY UPDATE `upload_timestamp` = ?', [$chapter->chapter_id, $chapter->manga_id, $chapter->upload_timestamp, $chapter->upload_timestamp]); } else { //update last_updated table by checking timestamps $last_updated = $sql->prep('chapter_undelete', ' SELECT * FROM mangadex_last_updated WHERE manga_id = ? AND lang_id = ? LIMIT 1 ', [$chapter->manga_id, $chapter->lang_id], 'fetch', PDO::FETCH_OBJ, -1); if ($chapter->upload_timestamp > $last_updated->upload_timestamp) { $sql->modify('chapter_undelete', " INSERT INTO mangadex_last_updated (chapter_id, manga_id, volume, chapter, title, upload_timestamp, user_id, lang_id, group_id, group_id_2, group_id_3, available) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE chapter_id = ?, volume = ?, chapter = ?, title = ?, upload_timestamp = ?, user_id = ?, group_id = ?, group_id_2 = ?, group_id_3 = ?, available = ? ", [$chapter->chapter_id, $chapter->manga_id, $chapter->volume, $chapter->chapter, $chapter->title, $chapter->upload_timestamp, $chapter->user_id, $chapter->lang_id, $chapter->group_id, $chapter->group_id_2, $chapter->group_id_3, $chapter->available, $chapter->chapter_id, $chapter->volume, $chapter->chapter, $chapter->title, $chapter->upload_timestamp, $chapter->user_id, $chapter->group_id, $chapter->group_id_2, $chapter->group_id_3, $chapter->available]); } } $memcached->delete("chapter_$id"); $memcached->delete("user_$chapter->user_id"); // Add undelete change to manga history $sql->modify('manga_history', 'INSERT INTO mangadex_manga_history (`user_id`, `manga_id`, `timestamp`, `action`, `changes`) VALUES (?,?,?,?,?)', [$user->user_id ?? 1, $chapter->manga_id, time(), 'chapter_undelete', json_encode([sprintf('Undelete Chapter %1$d ', $chapter->chapter_id)])]); $details = $id; } else { $details = "You can't restore Chapter #$id."; print display_alert("danger", "Failed", $details); // fail } } else { $details = "Chapter #$id does not exist."; print display_alert("danger", "Failed", $details); // fail } $result = (!is_numeric($details)) ? 0 : 1; break; case "chapter_delete": $id = prepare_numeric($_GET["id"]); $chapter = new Chapter($id); // Restriction check if (!validate_level($user, 'gmod') && $user->has_active_restriction(USER_RESTRICTION_CHAPTER_DELETE)) { $details = $user->get_restriction_message(USER_RESTRICTION_CHAPTER_DELETE) ?? "Chapter upload failed!"; print display_alert("danger", "Failed", $details); // fail } else if (count(get_object_vars($chapter))) { $group = new Group($chapter->group_id); if ( validate_level($user, 'gmod') || ( $chapter->available && ( $chapter->user_id == $user->user_id || ( !$group->group_is_inactive && ( $group->group_leader_id == $user->user_id || in_array($user->username, $group->get_members()) ) ) ) ) ) { $sql->modify('chapter_delete', " UPDATE mangadex_chapters SET chapter_deleted = 1 WHERE chapter_id = ? LIMIT 1 ", [$id]); $sql->modify('chapter_delete', " UPDATE mangadex_users SET user_uploads = user_uploads - 1 WHERE user_id = ? LIMIT 1 ", [$user->user_id]); //update last_updated table by deleting entry and replacing with an older entry if possible $sql->modify('chapter_delete', " DELETE FROM mangadex_last_updated WHERE chapter_id = ? LIMIT 1 ", [$id]); $last_updated = $sql->prep('chapter_delete', ' SELECT * FROM mangadex_chapters WHERE manga_id = ? AND lang_id = ? AND chapter_deleted = 0 ORDER BY upload_timestamp DESC LIMIT 1 ', [$chapter->manga_id, $chapter->lang_id], 'fetch', PDO::FETCH_OBJ, -1); if ($last_updated) $sql->modify('chapter_delete', " INSERT INTO mangadex_last_updated (chapter_id, manga_id, volume, chapter, title, upload_timestamp, user_id, lang_id, group_id, group_id_2, group_id_3, available) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE chapter_id = ?, volume = ?, chapter = ?, title = ?, upload_timestamp = ?, user_id = ?, group_id = ?, group_id_2 = ?, group_id_3 = ?, available = ? ", [$last_updated->chapter_id, $last_updated->manga_id, $last_updated->volume, $last_updated->chapter, $last_updated->title, $last_updated->upload_timestamp, $last_updated->user_id, $last_updated->lang_id, $last_updated->group_id, $last_updated->group_id_2, $last_updated->group_id_3, $last_updated->available, $last_updated->chapter_id, $last_updated->volume, $last_updated->chapter, $last_updated->title, $last_updated->upload_timestamp, $last_updated->user_id, $last_updated->group_id, $last_updated->group_id_2, $last_updated->group_id_3, $last_updated->available]); // Delete this chapter from the delayed table as well $sql->modify('delayed_chapter_delete', 'DELETE FROM mangadex_delayed_chapters WHERE `chapter_id` = ?', [$id]); $memcached->delete("chapter_$id"); $memcached->delete("user_$user->user_id"); // Add delete change to manga history $sql->modify('manga_history', 'INSERT INTO mangadex_manga_history (`user_id`, `manga_id`, `timestamp`, `action`, `changes`) VALUES (?,?,?,?,?)', [$user->user_id ?? 1, $chapter->manga_id, time(), 'chapter_delete', json_encode([sprintf('Delete Chapter %1$d ', $chapter->chapter_id)])]); $details = $id; print display_alert("success", "Success", "Chapter $id has been deleted."); // success } else { if (!$chapter->available) $details = "Chapter #$id is unavailable."; else $details = "You can't delete Chapter #$id."; print display_alert("danger", "Failed", $details); // fail } } else { $details = "Chapter #$id does not exist."; print display_alert("danger", "Failed", $details); // fail } $result = (!is_numeric($details)) ? 0 : 1; break; case "chapter_edit": $id = prepare_numeric($_GET["id"]); $chapter = new Chapter($id); $manga_id = prepare_numeric($_POST["manga_id"]); $chapter_name = htmlentities($_POST["chapter_name"]); $volume_number = remove_padding(htmlentities($_POST["volume_number"])); $chapter_number = remove_padding(htmlentities($_POST["chapter_number"])); $group_id = prepare_numeric($_POST["group_id"]); $group_id_2 = prepare_numeric($_POST["group_id_2"]); $group_id_3 = prepare_numeric($_POST["group_id_3"]); $lang_id = prepare_numeric($_POST["lang_id"]); $old_file = $_FILES['file']['name']; $unavailable = isset($_POST["unavailable"]) ? 0 : 1; $server = $_POST["server"] ?? $chapter->server; $page_order = $_POST["page_order"] ?? $chapter->page_order; $user_id = $_POST["user_id"] ?? $chapter->user_id; if (isset($_POST["user_id"]) && !validate_level($user, 'mod')) { $error .= display_alert('danger', 'Failed', 'You may not change the uploader user ID.'); } // Verify volume number if (preg_match('/[^0-9\.]+/', $volume_number)) { $error .= display_alert('warning', 'Format error', 'The Volume Number field may only contain numbers and decimals. Please use the Chapter Name field for text input like Chapter titles.'); } // Verify chapter_number if (preg_match('/[^0-9\.]+/', $chapter_number)) { $error .= display_alert('warning', 'Format error', 'The Chapter Number field may only contain numbers and decimals. Please use the Chapter Name field for text input like Chapter titles.'); } // Verify chapter title if (stripos($chapter_name, 'ch. ') !== false || stripos($chapter_name, 'vol. ') !== false) { $error .= display_alert('warning', 'Format error', 'The Chapter name may not contain chapter or volume numbers. Please use the Volume / Chapter number field for that.'); } // Verify there is no end tag if (stripos($chapter_name, htmlentities(' ')) !== false || stripos($chapter_name, ' [end]') !== false) { $error .= display_alert('warning', 'Format error', 'The Chapter name may not contain an end tag. Last chapters are set in the manga entry.'); } $current_group = new Group($chapter->group_id); $current_members_array = $current_group->get_members(); $current_members_array2 = []; if($chapter->group_id_2){ $current_group2 = new Group($chapter->group_id_2); $current_members_array2 = $current_group2->get_members(); } $current_members_array3 = []; if($chapter->group_id_3){ $current_group3 = new Group($chapter->group_id_3); $current_members_array3 = $current_group3->get_members(); } $target_group = new Group($group_id); if (!$target_group->group_is_inactive) { $group_members_array = $target_group->get_members(); } else { $group_members_array = []; $target_group->group_leader_id = -1; } if ($group_id_2) { $target_group2 = new Group($group_id_2); if (!$target_group2->group_is_inactive) { $group_2_members_array = $target_group2->get_members(); } else { $group_2_members_array = []; $target_group2->group_leader_id = -1; } } else { $target_group2 = new stdClass(); $target_group2->group_control = 0; $group_2_members_array = []; } if ($group_id_3) { $target_group3 = new Group($group_id_3); if (!$target_group3->group_is_inactive) { $group_3_members_array = $target_group3->get_members(); } else { $group_3_members_array = []; $target_group3->group_leader_id = -1; } } else { $target_group3 = new stdClass(); $target_group3->group_control = 0; $group_3_members_array = []; } $validate_group_control = (!$target_group->group_control || $user->user_id == $target_group->group_leader_id || in_array($user->username, $group_members_array)); $validate_group2_control = (!$target_group2->group_control || $user->user_id == $target_group2->group_leader_id || in_array($user->username, $group_2_members_array)); $validate_group3_control = (!$target_group3->group_control || $user->user_id == $target_group3->group_leader_id || in_array($user->username, $group_3_members_array)); $same_multi_group_validate = ($group_id != $group_id_2 && $group_id != $group_id_3 && ((!$group_id_2 && !$group_id_3) || $group_id_2 != $group_id_3)); // Restriction check // User that cant delete chapters, cant edit chapters for obvious reasons if (!validate_level($user, 'gmod') && $user->has_active_restriction(USER_RESTRICTION_CHAPTER_DELETE)) { $details = $user->get_restriction_message(USER_RESTRICTION_CHAPTER_DELETE) ?? "Chapter edit failed!"; $error .= display_alert("danger", "Failed", $details); } else if ( $same_multi_group_validate && ( validate_level($user, 'mod') || ( ( $user->user_id == $chapter->user_id || ($user->user_id && in_array($user->user_id, [$chapter->group_leader_id, $chapter->group_leader_id_2, $chapter->group_leader_id_3])) || in_array($user->username, array_merge($current_members_array, $current_members_array2, $current_members_array3)) ) && ($validate_group_control && $validate_group2_control && $validate_group3_control) && $chapter->available ) ) ) { if ($old_file) { $zip = new ZipArchive; if ($_FILES["file"]) { if (!ENABLE_UPLOAD) { die('Upload is temporarily disabled.'); } $value = explode(".", $_FILES["file"]["name"]); $validate_extention = in_array(strtolower(end($value)), ALLOWED_CHAPTER_EXT); $validate_file_size = ($_FILES["file"]["size"] <= MAX_CHAPTER_FILESIZE) || validate_level($user, 'pr'); //check file size $validate_zip_file = true; // Check for zip bomb by limiting the uncompressed filesize to 200M, double whats allowed if (($actualSize = get_zip_originalsize($_FILES["file"]["tmp_name"])) > (2 * MAX_CHAPTER_FILESIZE)) { $validate_file_size = false; } if ($validate_extention && $validate_file_size && $zip->open($_FILES["file"]["tmp_name"])) { $chapter_hash = md5($manga_id . $timestamp); mkdir(ABS_DATA_BASEPATH . "/data/$chapter_hash"); $zip->extractTo(ABS_DATA_BASEPATH . "/data/$chapter_hash/"); $zip->close(); $files = read_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/"); $pages = count($files); if ($pages == 1 && is_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/$files[2]")) { //folder rename(ABS_DATA_BASEPATH . "/data/$chapter_hash/$files[2]", ABS_DATA_BASEPATH . "/data/$chapter_hash/folder"); //rename the dir $files = read_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/folder/"); foreach($files as $value) { rename(ABS_DATA_BASEPATH . "/data/$chapter_hash/folder/$value", ABS_DATA_BASEPATH . "/data/$chapter_hash/$value"); //move them all } rmdir(ABS_DATA_BASEPATH . "/data/$chapter_hash/folder"); $files = read_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/"); } elseif ($pages > 1 && is_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/$files[3]")) { $error .= display_alert("danger", "Failed", "Your .zip contains multiple folders."); //can't open zip } } else { $validate_zip_file = false; } if ($_FILES["file"]["error"]) $error .= display_alert("danger", "Failed", "Missing file? Code: (" . $_FILES["file"]["error"] . ")."); elseif (!$validate_file_size) $error .= display_alert("danger", "Failed", "File size exceeds 100 MB."); //too big elseif (!$validate_extention) $error .= display_alert("danger", "Failed", "A .$extension file, not a .zip."); //too big elseif (!$validate_zip_file) $error .= display_alert("danger", "Failed", "There's something wrong with your .zip file."); //can't open zip } } } elseif (!$validate_group_control) $error .= display_alert("danger", "Failed", "Group 1 have restricted uploads to members only."); //banned elseif (!$validate_group2_control) $error .= display_alert("danger", "Failed", "Group 2 have restricted uploads to members only."); //banned elseif (!$validate_group3_control) $error .= display_alert("danger", "Failed", "Group 3 have restricted uploads to members only."); //banned elseif (!$same_multi_group_validate) $error .= display_alert("danger", "Failed", "Identical groups detected."); //banned elseif (!$chapter->available) $error .= display_alert("danger", "Failed", "This chapter is unavailable."); //unavailable else $error .= display_alert("danger", "Failed", "You can't edit Chapter #$id."); if (!$error) { $sql->modify('chapter_edit', " UPDATE mangadex_chapters SET manga_id = ?, title = ?, volume = ?, chapter = ?, group_id = ?, group_id_2 = ?, group_id_3 = ?, lang_id = ?, server = ?, page_order = ?, user_id = ?, available = ? WHERE chapter_id = ? LIMIT 1 ", [$manga_id, $chapter_name, $volume_number, $chapter_number, $group_id, $group_id_2, $group_id_3, $lang_id, $server, $page_order, $user_id, $unavailable, $id]); $sql->modify('chapter_edit', " UPDATE mangadex_last_updated SET manga_id = ?, volume = ?, chapter = ?, title = ?, upload_timestamp = ?, user_id = ?, lang_id = ?, group_id = ?, group_id_2 = ?, group_id_3 = ?, available = ? WHERE chapter_id = ? LIMIT 1 ", [$manga_id, $volume_number, $chapter_number, $chapter_name, $chapter->upload_timestamp, $user_id, $lang_id, $group_id, $group_id_2, $group_id_3, $unavailable, $id]); if ($old_file) { $page_order = ""; natcasesort($files); $arr = array_values($files); foreach($arr as $key => $value) { $key++; $arr = explode(".", $value); $ext = strtolower(end($arr)); if (!in_array($ext, ALLOWED_IMG_EXT)) @unlink(ABS_DATA_BASEPATH . "/data/$chapter_hash/$value"); else { $sha256 = hash_file('sha256', ABS_DATA_BASEPATH . "/data/$chapter_hash/$value"); @rename(ABS_DATA_BASEPATH . "/data/$chapter_hash/$value", ABS_DATA_BASEPATH . "/data/$chapter_hash/$key-$sha256.$ext"); //rename them all numerically $page_order .= "$key-$sha256.$ext,"; } } $page_order = rtrim($page_order, ","); if (!$chapter->server && is_dir(ABS_DATA_BASEPATH . "/data/$chapter->chapter_hash")) rename(ABS_DATA_BASEPATH . "/data/$chapter->chapter_hash", ABS_DATA_BASEPATH . "/delete/$chapter->chapter_hash"); $sql->modify('chapter_edit', " UPDATE mangadex_chapters SET server = ?, chapter_hash = ?, page_order = ? WHERE chapter_id = ? LIMIT 1 ", [IMAGE_SERVER, $chapter_hash, $page_order, $id]); } $memcached->delete("chapter_$id"); // Additionally, we need to invalidate the manga cache the api uses, otherwise changes to the chapter wont show up in the reader immediately $bind = [0,(string)$manga_id]; $order = "(CASE volume WHEN '' THEN 1 END) DESC, abs(volume) DESC, abs(chapter) DESC, group_id ASC"; $key = "chapters_query_".hash_array($bind)."_orderby_".md5($order)."_offset_0"; // See /api/index.php L:39+ $res = $memcached->delete($key); $details = $id; } print $error; //returns "" or a message $result = ($error) ? 0 : 1; break; case "chapter_report": $id = prepare_numeric($_GET["id"]); $type = prepare_numeric($_POST["type_id"]); $info = htmlentities($_POST["info"]); $report_restriction = $user->has_active_restriction(USER_RESTRICTION_CREATE_REPORT) && !validate_level($user, 'mod'); if (validate_level($user, 'member') && !$report_restriction) { $chapter_reasons = array_filter((new Report_Reasons())->toArray(), function($reason) { return REPORT_TYPES[$reason['type_id']] === 'Chapter'; }); if (($chapter_reasons[$type]['is_info_required'] == 1 ?? false) && !$info) { $details = "Please give more information."; print display_alert("danger", "Failed", $details); //fail } else { $sql->modify('chapter_report', " INSERT IGNORE INTO mangadex_reports_chapters (report_id, report_chapter_id, report_timestamp, report_type, report_info, report_user_id, report_mod_user_id, report_conclusion) VALUES (NULL, ?, UNIX_TIMESTAMP(), ?, ?, ?, 0, 0) ", [$id, $type, $info, $user->user_id]); $memcached->delete('mod_report_count'); $details = $id; post_on_discord(DISCORD_WEBHOOK_REPORT, [ 'username' => $user->username, 'embeds' => [ [ 'title' => 'Chapter Report', 'description' => $chapter_reasons[$type]['text'] ?? 'Unknown report type', 'url' => URL . 'mod/chapter_reports/new', 'footer' => [ 'text' => $info ] ] ] ]); if (defined('DISCORD_REPORT_PING_COUNT') && defined('DISCORD_REPORT_PING_ROLE_ID') && !empty(DISCORD_REPORT_PING_COUNT)) { $report_count = $sql->prep('chapter_report_count', ' SELECT count(DISTINCT report_user_id) FROM mangadex_reports_chapters WHERE report_chapter_id = ? AND report_conclusion = 0 ', [$id], 'fetchColumn', '', -1); if ($report_count === DISCORD_REPORT_PING_COUNT) { post_on_discord(DISCORD_WEBHOOK_REPORT, [ 'username' => 'MangaDex', 'content' => "<@&" . DISCORD_REPORT_PING_ROLE_ID . "> Chapter $id has received $report_count unresolved reports.\n". URL . "chapter/$id", ]); } } } } elseif ($report_restriction) { $details = $user->get_restriction_message(USER_RESTRICTION_CREATE_REPORT) ?? "You can't report Chapter $id."; print display_alert("danger", "Failed", $details); //fail } else { $details = "You can't report Chapter $id."; print display_alert("danger", "Failed", $details); //fail } $result = (!is_numeric($details)) ? 0 : 1; break; case "chapter_report_accept_all": $id = prepare_numeric($_GET["id"]); if (validate_level($user, 'gmod')) { $sql->modify('chapter_report_accept_all', " UPDATE mangadex_reports_chapters SET report_conclusion = 1, report_mod_user_id = ? WHERE report_chapter_id = ? ", [$user->user_id, $id]); print display_alert("success", "Success", "All reports for chapter #$id accepted."); //success $memcached->delete('mod_report_count'); $details = $id; $result = 1; } break; case "chapter_report_accept": $id = prepare_numeric($_GET["id"]); if (validate_level($user, 'gmod')) { $sql->modify('chapter_report_accept', " UPDATE mangadex_reports_chapters SET report_conclusion = 1, report_mod_user_id = ? WHERE report_id = ? LIMIT 1 ", [$user->user_id, $id]); print display_alert("success", "Success", "Report #$id accepted."); //success $memcached->delete('mod_report_count'); $details = $id; $result = 1; } break; case "chapter_report_reject": $id = prepare_numeric($_GET["id"]); if (validate_level($user, 'gmod')) { $sql->modify('chapter_report_reject', " UPDATE mangadex_reports_chapters SET report_conclusion = 2, report_mod_user_id = ? WHERE report_id = ? LIMIT 1 ", [$user->user_id, $id]); print display_alert("success", "Success", "Report #$id rejected."); //success $memcached->delete('mod_report_count'); $details = $id; $result = 1; } break; case "upload_queue_accept": $id = prepare_numeric($_GET["id"]); $row = $sql->prep("queue_$id", " SELECT * FROM mangadex_upload_queue WHERE queue_id = ? ", [$id], 'fetch', PDO::FETCH_OBJ, -1); if (validate_level($user, 'mod')) { $sql->modify('upload_queue_accept', " UPDATE mangadex_upload_queue SET queue_conclusion = 1, queue_mod_user_id = ? WHERE queue_id = ? LIMIT 1 ", [$user->user_id, $id]); $sql->modify('upload_queue_accept', " UPDATE mangadex_chapters SET upload_timestamp = GREATEST(upload_timestamp, UNIX_TIMESTAMP()), available = 1 WHERE chapter_id = ? LIMIT 1 ", [$row->chapter_id]); $sql->modify('upload_queue_accept', " UPDATE mangadex_last_updated SET upload_timestamp = UNIX_TIMESTAMP(), available = 1 WHERE chapter_id = ? LIMIT 1 ", [$row->chapter_id]); $sql->modify('upload_queue_accept', " UPDATE mangadex_users SET user_uploads = user_uploads + 1 WHERE user_id = ? LIMIT 1 ", [$row->user_id]); print display_alert("success", "Success", "Queue #$id accepted."); //success $memcached->delete("user_{$row->user_id}"); $memcached->delete("chapter_{$row->chapter_id}"); $memcached->delete('mod_upload_queue_count'); $details = $id; $result = 1; } break; case "upload_queue_reject": $id = prepare_numeric($_GET["id"]); $row = $sql->prep("queue_$id", " SELECT * FROM mangadex_upload_queue WHERE queue_id = ? ", [$id], 'fetch', PDO::FETCH_OBJ, -1); $chapter = new Chapter($row->chapter_id); if (validate_level($user, 'mod')) { $sql->modify('upload_queue_reject', " UPDATE mangadex_upload_queue SET queue_conclusion = 2, queue_mod_user_id = ? WHERE queue_id = ? LIMIT 1 ", [$user->user_id, $id]); $sql->modify('upload_queue_reject', " UPDATE mangadex_chapters SET available = 1, chapter_deleted = 1 WHERE chapter_id = ? LIMIT 1 ", [$row->chapter_id]); //update last_updated table by deleting entry and replacing with an older entry if possible $sql->modify('upload_queue_reject', " DELETE FROM mangadex_last_updated WHERE chapter_id = ? LIMIT 1 ", [$row->chapter_id]); $last_updated = $sql->prep('upload_queue_reject', ' SELECT * FROM mangadex_chapters WHERE manga_id = ? AND lang_id = ? AND chapter_deleted = 0 ORDER BY upload_timestamp DESC LIMIT 1 ', [$chapter->manga_id, $chapter->lang_id], 'fetch', PDO::FETCH_OBJ, -1); if ($last_updated) $sql->modify('upload_queue_reject', " INSERT INTO mangadex_last_updated (chapter_id, manga_id, volume, chapter, title, upload_timestamp, user_id, lang_id, group_id, group_id_2, group_id_3, available) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE chapter_id = ?, volume = ?, chapter = ?, title = ?, upload_timestamp = ?, user_id = ?, group_id = ?, group_id_2 = ?, group_id_3 = ?, available = ? ", [$last_updated->chapter_id, $last_updated->manga_id, $last_updated->volume, $last_updated->chapter, $last_updated->title, $last_updated->upload_timestamp, $last_updated->user_id, $last_updated->lang_id, $last_updated->group_id, $last_updated->group_id_2, $last_updated->group_id_3, $last_updated->available, $last_updated->chapter_id, $last_updated->volume, $last_updated->chapter, $last_updated->title, $last_updated->upload_timestamp, $last_updated->user_id, $last_updated->group_id, $last_updated->group_id_2, $last_updated->group_id_3, $last_updated->available]); // Delete this chapter from the delayed table as well $sql->modify('delayed_chapter_delete', 'DELETE FROM mangadex_delayed_chapters WHERE `chapter_id` = ?', [$row->id]); $memcached->delete("chapter_{$row->chapter_id}"); $memcached->delete('mod_upload_queue_count'); print display_alert("success", "Success", "Queue #$id rejected."); //success $details = $id; $result = 1; } break; case "chapter_upload": if (!ENABLE_UPLOAD) { die('Upload is temporarily disabled.'); } $zip = new ZipArchive; $manga_id = prepare_numeric($_POST["manga_id"]); $chapter_name = htmlentities($_POST["chapter_name"]); $volume_number = remove_padding(htmlentities($_POST["volume_number"])); $chapter_number = remove_padding(htmlentities($_POST["chapter_number"])); $group_id = prepare_numeric($_POST["group_id"]); $group_id_2 = !empty($_POST["group_id_2"]) ? prepare_numeric($_POST["group_id_2"]) : 0; $group_id_3 = !empty($_POST["group_id_3"]) ? prepare_numeric($_POST["group_id_3"]) : 0; $lang_id = prepare_numeric($_POST["lang_id"]); $external = isset($_POST["external"]) ? remove_padding(htmlentities($_POST["external"])) : ''; $is_deleted = (bool) ( isset($_POST["is_deleted"]) && validate_level($user, 'gmod') && $_POST["is_deleted"] ); $available = (isset($_POST["unavailable"]) && validate_level($user, 'gmod')) ? 0 : 1; $override_user_id = (isset($_POST["override_user_id"]) && !empty($_POST["override_user_id"]) && validate_level($user, 'gmod')) ? prepare_numeric($_POST["override_user_id"]) : false; // Verify volume number if (preg_match('/[^0-9\.]+/', $volume_number)) { $error .= display_alert('warning', 'Format error', 'The Volume Number field may only contain numbers and decimals. Please use the Chapter Name field for text input like Chapter titles.'); } // Verify chapter_number if (preg_match('/[^0-9\.]+/', $chapter_number)) { $error .= display_alert('warning', 'Format error', 'The Chapter Number field may only contain numbers and decimals. Please use the Chapter Name field for text input like Chapter titles.'); } // Verify chapter title if (stripos($chapter_name, 'ch. ') !== false || stripos($chapter_name, 'vol. ') !== false) { $error .= display_alert('warning', 'Format error', 'The Chapter name may not contain chapter or volume numbers. Please use the Volume / Chapter number field for that.'); } // Verify there is no end tag if (stripos($chapter_name, htmlentities(' ')) !== false || stripos($chapter_name, ' [end]') !== false) { $error .= display_alert('warning', 'Format error', 'The Chapter name may not contain an end tag. Last chapters are set in the manga entry.'); } $manga = new Manga($manga_id); $group = new Group($group_id); $group_members_array = $group->get_members(); if ($group_id_2) { $group2 = new Group($group_id_2); $group2_members_array = $group2->get_members(); } else { $group2 = new stdClass(); $group2->group_control = 0; } if ($group_id_3) { $group3 = new Group($group_id_3); $group3_members_array = $group3->get_members(); } else { $group3 = new stdClass(); $group3->group_control = 0; } $validate_group_control = (!$group->group_control || $user->user_id == $group->group_leader_id || ($group->group_control && in_array($user->username, $group_members_array))); $validate_group2_control = (!$group2->group_control || $user->user_id == $group2->group_leader_id || ($group2->group_control && in_array($user->username, $group2_members_array))); $validate_group3_control = (!$group3->group_control || $user->user_id == $group3->group_leader_id || ($group3->group_control && in_array($user->username, $group3_members_array))); $same_multi_group_validate = ($group_id != $group_id_2 && $group_id != $group_id_3 && ((!$group_id_2 && !$group_id_3) || $group_id_2 != $group_id_3)); if (!validate_level($user, 'gmod') && $user->has_active_restriction(USER_RESTRICTION_CHAPTER_UPLOAD)) { $details = $user->get_restriction_message(USER_RESTRICTION_CHAPTER_UPLOAD) ?? "Chapter upload failed!"; $error .= display_alert("danger", "Failed", $details); //timed_out } $remote = new RemoteFileUploader(); $remote->setContext(['userid' => $user->user_id]); $isRemoteUploadError = false; $wasRemoteUploaded = false; if (empty($error) && isset($_POST['fileurl']) && !empty($_POST['fileurl']) && validate_level($user, 'gl')) { if (!$remote->supports($_POST['fileurl'])) { $error .= display_alert("danger", "Failed", 'This file url is not supported'); } else { try { ['filename' => $filename, 'filetype' => $filetype, 'filetmpname' => $filetmpname, 'filesize' => $filesize ] = $remote->downloadFromRemote($_POST['fileurl']); $_FILES["file"] = [ 'name' => $filename, 'type' => $filetype, 'tmp_name' => $filetmpname, 'error' => 0, 'size' => $filesize, ]; $wasRemoteUploaded = true; } catch (RemoteFileUploadFailed $e) { $error .= display_alert('danger', 'Failed', $e->getMessage()); } catch (\Throwable $e) { // send to sentry trigger_error($e->getMessage(), E_USER_WARNING); $error .= display_alert("danger", "Failed", 'Remote file upload failed'); } } } if (empty($error) && (!isset($_FILES['file']['tmp_name']) || empty($_FILES['file']['tmp_name']))) { $error .= display_alert("danger", "Failed", 'No upload found.'); } if (empty($error) && validate_level($user, 'member') && count(get_object_vars($manga)) && (($validate_group_control && $validate_group2_control && $validate_group3_control) || validate_level($user, 'gmod')) && $same_multi_group_validate) { if ($external) $chapter_hash = md5($manga_id . $chapter_name . $volume_number . $chapter_number . $timestamp); elseif ($_FILES["file"]) { $value = explode(".", $_FILES["file"]["name"]); $validate_extention = in_array(strtolower(end($value)), ALLOWED_CHAPTER_EXT); $validate_file_size = ($_FILES["file"]["size"] <= MAX_CHAPTER_FILESIZE) || validate_level($user, 'pr'); //check file size $validate_zip_file = true; // Check for zip bomb by limiting the uncompressed filesize to 200M, double whats allowed if (($actualSize = get_zip_originalsize($_FILES["file"]["tmp_name"])) > (2 * MAX_CHAPTER_FILESIZE)) { $validate_file_size = false; } if ($validate_extention && $validate_file_size && !empty($_FILES["file"]["tmp_name"]) && $zip->open($_FILES["file"]["tmp_name"]) === true) { $chapter_hash = md5($manga_id . $chapter_name . $volume_number . $chapter_number . $timestamp); mkdir(ABS_DATA_BASEPATH . "/data/$chapter_hash"); $zip->extractTo(ABS_DATA_BASEPATH . "/data/$chapter_hash/"); $zip->close(); if ($wasRemoteUploaded) { @unlink($_FILES["file"]["tmp_name"]); } $files = read_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/"); $pages = count($files); if ($pages == 1 && is_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/$files[2]")) { //folder rename(ABS_DATA_BASEPATH . "/data/$chapter_hash/$files[2]", ABS_DATA_BASEPATH . "/data/$chapter_hash/folder"); //rename the dir $files = read_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/folder/"); foreach($files as $value) { rename(ABS_DATA_BASEPATH . "/data/$chapter_hash/folder/$value", ABS_DATA_BASEPATH . "/data/$chapter_hash/$value"); //move them all } rmdir(ABS_DATA_BASEPATH . "/data/$chapter_hash/folder"); $files = read_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/"); } elseif ($pages > 1 && is_dir(ABS_DATA_BASEPATH . "/data/$chapter_hash/$files[3]")) { $error .= display_alert("danger", "Failed", "Your .zip contains multiple folders, or you have files starting with an invalid character (!)"); //can't open zip } elseif ($pages < 1) { $error .= display_alert('danger', 'Failed', "Your .zip did not contain any files."); } } else { $validate_zip_file = false; } if (isset($_FILES["file"]["error"]) && !empty($_FILES["file"]["error"])) $error .= display_alert("danger", "Failed", "Missing file? Code: (" . $_FILES["file"]["error"] . ")."); elseif (!$validate_file_size) $error .= display_alert("danger", "Failed", "File size exceeds 100 MB."); //too big elseif (!$validate_extention) $error .= display_alert("danger", "Failed", "Only .zip archvies are supported."); //too big elseif (!$validate_zip_file) $error .= display_alert("danger", "Failed", "There's something wrong with your .zip file."); //can't open zip } else $error .= display_alert("danger", "Failed", "Missing file."); //missing image } elseif (empty($error)) { if (!$user->user_id) $error .= display_alert("danger", "Failed", "Your session has timed out. Please log in again."); //timed_out elseif (!$user->level_id) $error .= display_alert("danger", "Failed", "You're banned from uploading!"); //banned elseif (!$validate_group_control) $error .= display_alert("danger", "Failed", "Group 1 have restricted uploads to members only."); //banned elseif (!$validate_group2_control) $error .= display_alert("danger", "Failed", "Group 2 have restricted uploads to members only."); //banned elseif (!$validate_group3_control) $error .= display_alert("danger", "Failed", "Group 3 have restricted uploads to members only."); //banned elseif (!$same_multi_group_validate) $error .= display_alert("danger", "Failed", "Identical groups detected."); //banned elseif (!$stop) $error .= display_alert("danger", "Failed", "Stop uploading for a while due to image transfer."); //banned else $error .= display_alert("danger", "Failed", "Manga #$manga_id does not exist."); } //if no errors, then upload if (!$error) { if ($external) { $page_order = $external; } else { $page_order = ""; natcasesort($files); $arr = array_values($files); //$letter = rand_letter(1); foreach($arr as $key => $value) { $key++; $arr = explode(".", $value); $ext = strtolower(end($arr)); if (!in_array($ext, ALLOWED_IMG_EXT)) @unlink(ABS_DATA_BASEPATH . "/data/$chapter_hash/$value"); else { $sha256 = hash_file('sha256', ABS_DATA_BASEPATH . "/data/$chapter_hash/$value"); @rename(ABS_DATA_BASEPATH . "/data/$chapter_hash/$value", ABS_DATA_BASEPATH . "/data/$chapter_hash/$key-$sha256.$ext"); //rename them all numerically $page_order .= "$key-$sha256.$ext,"; } } $page_order = rtrim($page_order, ","); } $upload_timestamp = $timestamp + $group->group_delay; $is_delayed = $group->group_delay > 0; $user_has_available_uploads = validate_level($user, 'pr') || $sql->prep('chapter_queue_check', ' SELECT count(chapter_id) FROM mangadex_chapters WHERE user_id = ? AND lang_id = ? AND available = 1 AND chapter_deleted = 0 LIMIT 1 ', [$user->user_id, $lang_id], 'fetchColumn', '', -1); $available = !$user_has_available_uploads ? 0 : $available; $available = (in_array($manga_id, DNU_MANGA_IDS) && $lang_id == 1) ? 0 : $available; $uploader = $override_user_id ?: $user->user_id; $chapter_id = $sql->modify('chapter_upload', " INSERT INTO mangadex_chapters (chapter_id, chapter_hash, manga_id, volume, chapter, title, upload_timestamp, user_id, chapter_views, lang_id, authorised, group_id, group_id_2, group_id_3, server, page_order, chapter_deleted, thread_id, available) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, 0, ?, 0, ?, ?, ?, ?, ?, ?, 0, ?) ", [$chapter_hash, $manga_id, $volume_number, $chapter_number, $chapter_name, $upload_timestamp, $uploader, $lang_id, $group_id, $group_id_2, $group_id_3, IMAGE_SERVER, $page_order, $is_deleted ? 1 : 0, $available ? 1 : 0]); if (!$user_has_available_uploads) { if (defined('DISCORD_REPORT_PING_ROLE_ID')) { $queued_chapter_count = $sql->prep('chapter_queue_check', ' SELECT count(DISTINCT chapter_id) FROM mangadex_upload_queue WHERE user_id = ? AND queue_conclusion IS NULL ', [$user->user_id], 'fetchColumn', '', -1); if ($queued_chapter_count === 0) { post_on_discord(DISCORD_WEBHOOK_REPORT, [ 'username' => 'MangaDex', 'content' => "<@&" . DISCORD_REPORT_PING_ROLE_ID . "> Chapter $chapter_id has been held in the upload queue.\n". URL . "user/$user->user_id \n". URL . "chapter/$chapter_id", ]); } } $sql->modify('chapter_upload', " INSERT INTO mangadex_upload_queue (queue_id, user_id, chapter_id) VALUES (NULL, ?, ?) ", [$user->user_id, $chapter_id]); $memcached->delete('mod_upload_queue_count'); } if (!$is_deleted) $sql->modify('chapter_upload', " UPDATE mangadex_groups SET group_last_updated = ? WHERE group_id = ? LIMIT 1 ", [$timestamp, $group_id]); if ($is_delayed && !$is_deleted) { // Add this chapter to the delayed_chapters table, so metadata can be modified at a later date if ($chapter_id > 0) { $sql->modify('chapter_delayed', 'INSERT INTO mangadex_delayed_chapters (`chapter_id`, `manga_id`, `upload_timestamp`) VALUES (?,?,?)', [$chapter_id, $manga_id, $upload_timestamp]); } } else if (!$is_deleted) { $sql->modify('chapter_upload', " UPDATE mangadex_mangas SET manga_last_uploaded = ? WHERE manga_id = ? LIMIT 1 ", [$timestamp, $manga_id]); $sql->modify('chapter_upload', " INSERT INTO mangadex_last_updated (chapter_id, manga_id, volume, chapter, title, upload_timestamp, user_id, lang_id, group_id, group_id_2, group_id_3, available) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE chapter_id = ?, volume = ?, chapter = ?, title = ?, upload_timestamp = ?, user_id = ?, group_id = ?, group_id_2 = ?, group_id_3 = ?, available = ? ", [$chapter_id, $manga_id, $volume_number, $chapter_number, $chapter_name, $upload_timestamp, $uploader, $lang_id, $group_id, $group_id_2, $group_id_3, $available ? 1 : 0, $chapter_id, $volume_number, $chapter_number, $chapter_name, $upload_timestamp, $uploader, $group_id, $group_id_2, $group_id_3, $available ? 1 : 0]); } if ($group_id_2 && !$is_deleted) $sql->modify('chapter_upload', " UPDATE mangadex_groups SET group_last_updated = ? WHERE group_id = ? LIMIT 1 ", [$timestamp, $group_id_2]); if ($group_id_3 && !$is_deleted) $sql->modify('chapter_upload', " UPDATE mangadex_groups SET group_last_updated = ? WHERE group_id = ? LIMIT 1 ", [$timestamp, $group_id_3]); if (!$is_deleted && $user->user_uploads) $sql->modify('chapter_upload', " UPDATE mangadex_users SET user_uploads = user_uploads + 1 WHERE user_id = ? LIMIT 1 ", [$user->user_id]); $memcached->delete("user_$user->user_id"); } print $error; //returns "" or a message $result = ($error) ? 0 : 1; break; }