if (!function_exists('book_with_chapters_lp')) { function book_with_chapters_lp(string $path): string { return function_exists('lp') ? lp($path) : ((string) ($GLOBALS['ztest'] ?? '') . $path); } } require_once __DIR__ . '/my_mysqli.php'; require_once __DIR__ . '/BookChapterPermissions.php'; use app\helpers\MysqliPrepared; require_once __DIR__ . '/chapter_delay_access.php'; if (!function_exists('BookWithChaptersCanAccessAuthorChapter')) { function BookWithChaptersCanAccessAuthorChapter(array $chapterRow, int $currentUserId, array $adminIds): bool { if ($currentUserId <= 0) { return false; } $adminLookup = array_flip(array_map('intval', $adminIds)); if (isset($adminLookup[$currentUserId])) { return true; } $authorIds = [ (int) ($chapterRow['user_id'] ?? 0), (int) ($chapterRow['author2id'] ?? 0), (int) ($chapterRow['author3id'] ?? 0), ]; $chapterWriterId = (int) ($chapterRow['written_by'] ?? 0); if ($chapterWriterId > 0) { $authorIds[] = $chapterWriterId; } foreach ($authorIds as $authorId) { if ($authorId > 0 && $authorId === $currentUserId) { return true; } } return false; } } // ini_set('display_errors', '1'); // ini_set('display_startup_errors', '1'); // error_reporting(E_ALL); if (!function_exists('book_with_chapters_can_manage_delayed_chapter')) { function book_with_chapters_can_manage_delayed_chapter(array $row, int $viewerId, array $adminIds = []): bool { if ($viewerId <= 0) { return false; } $adminLookup = array_flip(array_map('intval', $adminIds)); if (isset($adminLookup[$viewerId])) { return true; } if ((int) ($row['user_id'] ?? 0) === $viewerId) { return true; } $coauthorRules = [ ['id' => 'author2id', 'rate' => 'author2rate'], ['id' => 'author3id', 'rate' => 'author3rate'], ]; foreach ($coauthorRules as $rule) { $coauthorId = (int) ($row[$rule['id']] ?? 0); $coauthorRate = (int) ($row[$rule['rate']] ?? 0); if ($coauthorId === $viewerId && $coauthorRate > 0 && $coauthorRate !== 100) { return true; } } return (int) ($row['written_by'] ?? 0) === $viewerId; } } function BookWithChapters($book_id, $chapter_id) { global $treeya, $log; global $ImgPrefix; global $realname; global $loggedin; global $since; global $login; global $id; global $z_admins; global $Link; global $MasterLink; global $menuitems; global $gitem; global $goperation; global $gother; global $submenu; global $submenutxt; global $cash; global $bookgenres; global $genrenames; global $userpic; global $gseriesname; global $gAggregatedRating; global $canonical; global $isbanned; global $usersettings; global $userkarma; global $userro; global $brief; global $zelluloza_site; global $ztest; global $prefetchedCommentsByParent; $bwcMyPath = book_with_chapters_lp('/my/'); $esc = static fn ($s) => htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $renderError = static function (): string { http_response_code(500); return "
"; }; $escapeHtml = static function ($value) { return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }; $escapeChapterTitle = static function ($value) use ($escapeHtml) { $allowedTags = ['b', 'strong', 'i', 'em', 'u']; $allowedTagString = '<' . implode('><', $allowedTags) . '>'; $clean = strip_tags((string) $value, $allowedTagString); $escaped = $escapeHtml($clean); foreach ($allowedTags as $tag) { $escaped = str_replace( ["<{$tag}>", "</{$tag}>"], ["<{$tag}>", "{$tag}>"], $escaped ); } return $escaped; }; $formatBookDateTime = static function ($value) { $timestamp = strtotime((string) $value); if ($timestamp === false) { return (string) $value; } $months = [ 1 => 'января', 2 => 'февраля', 3 => 'марта', 4 => 'апреля', 5 => 'мая', 6 => 'июня', 7 => 'июля', 8 => 'августа', 9 => 'сентября', 10 => 'октября', 11 => 'ноября', 12 => 'декабря', ]; $month = $months[(int) date('n', $timestamp)] ?? date('m', $timestamp); return date('j', $timestamp) . ' ' . $month . ' ' . date('Y в H:i', $timestamp); }; $canShowChapterAuthorMenu = static function (array $row) use ($id, $z_admins): bool { return BookWithChaptersCanAccessAuthorChapter($row, (int) $id, (array) $z_admins); }; $canUseRestrictedChapterActions = static function (array $row, int $writtenBy) use ($id, $z_admins): bool { $currentUserId = (int) $id; return in_array($currentUserId, array_map('intval', $z_admins), true) || $currentUserId === (int) ($row['user_id'] ?? 0) || ($writtenBy > 0 && $currentUserId === $writtenBy); }; $book_id = (int) $book_id; $chapter_id = (int) $chapter_id; if ($book_id <= 0) { return; } // Проверка соединения с БД перед использованием if (!$Link || !($Link instanceof mysqli)) { if (isset($log) && is_object($log) && method_exists($log, 'error')) { $log->error('Invalid mysqli connection in BookWithChapters'); } else { error_log('[BookWithChapters] Invalid mysqli connection'); } return $renderError(); } // Сохраняем числовые значения для использования в подготовленных запросах $book_id_int = $book_id; $chapter_id_int = $chapter_id; // Экранируем для использования в обычных запросах (для обратной совместимости) $book_id = mysqli_real_escape_string($Link, (string) $book_id); $chapter_id = mysqli_real_escape_string($Link, (string) $chapter_id); $newversionalert = ''; $rdate = ''; $cntr = ''; $chapter_durs = ''; $og_stat = ''; $og_stat_email = ''; echo enqueue_owl(); echo ""; echo ""; echo ""; echo ""; // echo ""; $chap_filter = 'order by chapter_id'; if ($chapter_id != 0) { $chap_filter = "and chapter_id=$chapter_id"; } $purchased = []; $bookPurchased = false; $bookPaid = 1; $Query = 'SELECT /*091*/ purchases.chapter_id, purchases.paid, book_chapters.id AS chapter_ref_id, book_chapters.version0_id FROM purchases LEFT JOIN book_chapters ON book_chapters.book_id = purchases.book_id AND book_chapters.id = purchases.chapter_id WHERE purchases.user_id=? AND purchases.book_id=? AND purchases.bonus_buy IN (' . BONUS_BUY_ALLOWED_VALUES . ')'; $Result = MysqliPrepared::select($Link, $Query, 'ii', [(int) $id, (int) $book_id]); if (!$Result) { $log->error('Database query error in BookWithChapters: ' . mysqli_error($Link) . ' Query: ' . $Query); return $renderError(); } while ($Row = mysqli_fetch_assoc($Result)) { if ($Row['chapter_id'] == 0) { $bookPurchased = true; $bookPaid = 1 + $Row['paid']; } else { $origId = (int) ($Row['version0_id'] ?: ($Row['chapter_ref_id'] ?: $Row['chapter_id'])); $purchased[$origId] = 1 + $Row['paid']; } } mysqli_free_result($Result); if ($bookPurchased) { $Result = MysqliPrepared::select($Link, 'SELECT id FROM book_chapters WHERE book_id=? AND added != 0', 'i', [(int) $book_id]); while ($Row = mysqli_fetch_assoc($Result)) { $purchased[$Row['id']] = $bookPaid; } mysqli_free_result($Result); } // число читателей главы в данный момент $fbcomms = []; $cntreaders = []; if ($chapter_id == 0) { $Query = 'SELECT /*092*/ chapter_id, COUNT(chapter_id) AS cnt FROM bookmarks WHERE book_id=? AND bm_type=0 GROUP BY chapter_id ORDER BY chapter_id'; $Result = MysqliPrepared::select($Link, $Query, 'i', [(int) $book_id]); } else { $Query = 'SELECT /*093*/ chapter_id, COUNT(chapter_id) AS cnt FROM bookmarks WHERE book_id=? AND bm_type=0 AND chapter_id=?'; $Result = MysqliPrepared::select($Link, $Query, 'ii', [(int) $book_id, (int) $chapter_id]); } if (!$Result) { $log->error('Database query error in BookWithChapters: ' . mysqli_error($Link) . ' Query: ' . $Query); return $renderError(); } while ($Row = mysqli_fetch_assoc($Result)) { $cntreaders[$Row['chapter_id']] = $Row['cnt']; } mysqli_free_result($Result); // отзывы к главам $chaptercomms = []; $chaptercomms_list = []; $chap_filter2 = ''; $chapFilter2Types = ''; $chapFilter2Params = []; if ($chapter_id != 0) { $chap_filter2 = ' AND chapter_id=?'; $chapFilter2Types = 'i'; $chapFilter2Params = [(int) $chapter_id]; } // PlsLoginMessage(); $Result = MysqliPrepared::select($MasterLink, 'SELECT form FROM books WHERE id=?', 'i', [(int) $book_id]); if (!$Result) { $log->error('Database query error in BookWithChapters: ' . mysqli_error($MasterLink)); return $renderError(); } $book_form = ''; while ($Row = mysqli_fetch_assoc($Result)) { $book_form = $Row['form']; } $chapterCommentsPageSize = 20; $chapterCommentsCountQuery = "SELECT /*094-count*/ chaptercomm.chapter_id, COUNT(*) AS total FROM chaptercomm JOIN book_chapters ON book_chapters.id=chapter_id AND book_id=?{$chap_filter2} WHERE complaints < 2 AND chaptercomm.parent_id = 0 AND book_chapters.is_draft=0 GROUP BY chaptercomm.chapter_id"; $chapterCommTotals = []; $Result = MysqliPrepared::select($MasterLink, $chapterCommentsCountQuery, 'i' . $chapFilter2Types, array_merge([(int) $book_id], $chapFilter2Params)); if ($Result) { while ($Row = mysqli_fetch_assoc($Result)) { $chapterCommTotals[(int) $Row['chapter_id']] = (int) $Row['total']; } mysqli_free_result($Result); } else { $log->warning('Chapter comments count query failed in BookWithChapters: ' . mysqli_error($MasterLink)); } $Query = "SELECT /*094*/ book_chapters.id, chaptercomm.user_id, realname, books.name, chaptercomm.chapter_id, complaints, users.pic AS pic, chaptercomm.id AS cid, chaptercomm.text, chaptercomm.added, DATE_FORMAT(chaptercomm.added, '%Y-%m-%d %T %z') AS added2, written_by, finished, books.user_id AS bookauthor, chaptercomm.vote_like, chaptercomm.vote_dis, users.id AS uid, timestampdiff(minute, chaptercomm.added, now()) AS inminutes, usr_compl.chaptercomm_id AS compl, IFNULL(paid,-1) AS paid, bonus_buy, karma FROM chaptercomm JOIN book_chapters ON book_chapters.id=chapter_id AND book_id=?{$chap_filter2} JOIN users ON users.id=user_id JOIN books ON books.id=? LEFT JOIN purchases ON purchases.user_id=chaptercomm.user_id AND purchases.book_id=? AND bonus_buy IN (" . BONUS_BUY_ALLOWED_VALUES . ") AND (purchases.chapter_id=0 OR purchases.chapter_id=IFNULL(version0_id, chaptercomm.chapter_id)) LEFT JOIN usr_compl ON usr_compl.chaptercomm_id=chaptercomm.id AND usr_compl.user_id=? WHERE complaints < 2 AND chaptercomm.parent_id = 0 AND book_chapters.is_draft=0 ORDER BY added DESC LIMIT {$chapterCommentsPageSize}"; $commentsBindTypes = 'i' . $chapFilter2Types . 'iii'; $commentsBindParams = array_merge([(int) $book_id], $chapFilter2Params, [(int) $book_id, (int) $book_id, (int) $id]); $Result = MysqliPrepared::select($MasterLink, $Query, $commentsBindTypes, $commentsBindParams); if (!$Result) { $log->error('Database query error in BookWithChapters: ' . mysqli_error($Link) . ' Query: ' . $Query); return $renderError(); } $rootComments = []; $chapterComments = []; $commentIds = []; while ($Row = mysqli_fetch_assoc($Result)) { $chapterComments[] = $Row; $commentIds[] = (int) $Row['cid']; $rootComments[] = $Row; } mysqli_free_result($Result); $commentLikes = []; if (!empty($commentIds)) { $commentPlaceholders = implode(',', array_fill(0, count($commentIds), '?')); $sql_vote_og = "SELECT comment_id, mark FROM chaptercomm_likes WHERE comment_id IN ({$commentPlaceholders})"; $sql_query_og = MysqliPrepared::select($Link, $sql_vote_og, str_repeat('i', count($commentIds)), $commentIds); if (!$sql_query_og) { $log->error('Database query error in BookWithChapters (comment likes): ' . mysqli_error($Link) . ' Query: ' . $sql_vote_og); return $renderError(); } while ($Row = mysqli_fetch_assoc($sql_query_og)) { $commentLikes[$Row['comment_id']] = $Row['mark']; } mysqli_free_result($sql_query_og); } // Подгружаем ответы на все комментарии одним проходом по дереву $prefetchedCommentsByParent = []; $parentsToFetch = array_column($rootComments, 'cid'); while (!empty($parentsToFetch)) { $parentsToFetch = array_values(array_filter(array_unique(array_map('intval', $parentsToFetch)))); if ($parentsToFetch === []) { break; } $parentPlaceholders = implode(',', array_fill(0, count($parentsToFetch), '?')); $childQuery = "SELECT /*131-5-bulk*/chaptercomm.id AS chap_id, chaptercomm.parent_id, book_chapters.title, chaptercomm.chapter_id, pic, books.user_id AS bookauthor, books.id AS cur_book_id, chaptercomm.vote_like, chaptercomm.vote_dis, books.form AS bookform, finished, written_by, DATE_FORMAT(chaptercomm.added, '%d/%m/%Y %T') AS added, chaptercomm.text, users.realname, users.id AS uid, nocomms, IFNULL(paid,-1) AS paid, timestampdiff(minute, chaptercomm.added, now()) AS inminutes FROM chaptercomm JOIN book_chapters ON book_chapters.id = chaptercomm.chapter_id JOIN books ON books.id = book_chapters.book_id JOIN users ON users.id = chaptercomm.user_id LEFT JOIN purchases ON purchases.user_id=chaptercomm.user_id AND purchases.book_id=? AND bonus_buy != 1 AND purchases.chapter_id=IF(version0_id,version0_id,chaptercomm.chapter_id) WHERE book_chapters.book_id = ? AND complaints < 2 AND chaptercomm.text != '' AND chaptercomm.parent_id IN ({$parentPlaceholders}) ORDER BY chaptercomm.parent_id, chaptercomm.added DESC"; $childBindTypes = 'ii' . str_repeat('i', count($parentsToFetch)); $childBindParams = array_merge([(int) $book_id, (int) $book_id], $parentsToFetch); $childResult = MysqliPrepared::select($MasterLink, $childQuery, $childBindTypes, $childBindParams); if (!$childResult) { $log->error('Database query error in BookWithChapters (child comments): ' . mysqli_error($MasterLink) . ' Query: ' . $childQuery); return $renderError(); } $nextLevelParents = []; while ($childRow = mysqli_fetch_assoc($childResult)) { $parentId = (int) $childRow['parent_id']; if (!isset($prefetchedCommentsByParent[$parentId])) { $prefetchedCommentsByParent[$parentId] = []; } if (count($prefetchedCommentsByParent[$parentId]) >= 10) { continue; } $prefetchedCommentsByParent[$parentId][] = $childRow; $nextLevelParents[] = (int) $childRow['chap_id']; } mysqli_free_result($childResult); $parentsToFetch = $nextLevelParents; } foreach ($chapterComments as $Row) { //*получаем найличие лайков на комментарий $vote_status_like = 123; //передаем в ajax, чтобы не было лишних запросов $vote_status_dis = 123; //передаем в ajax, чтобы не было лишних запросов $row_og = ''; if (isset($commentLikes[$Row['cid']])) { $row_og = $commentLikes[$Row['cid']] === '-1' ? 'active_dis' : 'active_like'; $vote_status_like = $row_og === 'active_like' ? 'true' : 'false'; //передаем в ajax, чтобы не было лишних запросов $vote_status_dis = $row_og === 'active_dis' ? 'true' : 'false'; //передаем в ajax, чтобы не было лишних запросов } //конец $comment_raw = (string) $Row['text']; $comment = $esc($comment_raw); if (trim($comment_raw) === '') { continue; } $rid = $Row['id']; if (!isset($chaptercomms[$Row['chapter_id']])) { $chaptercomms[$Row['chapter_id']] = []; $chaptercomms_list[$Row['chapter_id']] = []; } $report = ['vis', 'hid']; if ($Row['compl'] != null) { // отправлял ли комплейнт этот юзер $report[0] = 'hid'; $report[1] = 'vis'; } $nonce1 = create_nonce('ajax_complaint'); //$nonce2 = create_nonce('ajax_setreturn'); $editmycomment = ''; $edit_comment_btn = ''; $canManageComment = ((int) $Row['user_id'] === (int) $id) || in_array($id, $z_admins, true); if ($canManageComment) { $edit_comment_btn = <<
", $reduced); //smiles
$reduced = preg_replace('/:jm(1?[0-9]{2})/', "
", $reduced); //smiles
$comment1 = preg_replace('/:sm([0-9]{2})/', "
", $comment1); //smiles
$comment1 = preg_replace('/:jm(1?[0-9]{2})/', "
", $comment1); //smiles
$reducedcomm .= $reduced;
$reducedcomm .= <<
", $comment1); //smiles
$comment1 = preg_replace('/:jm(1?[0-9]{2})/', "
", $comment1); //smiles
$reducedcomm = $comment1;
}
$val .= <<