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}>", ""], $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 = << END; } $comment1 = preg_replace_callback('/id:([0-9]*)/i', 'Forum_ureplacer_link', $comment); $comment1 = preg_replace_callback('/ch:([0-9]*)/i', 'Forum_creplacer_link', $comment1); $comment1 = preg_replace( ['/\*([^\*]*)\*/i', '/\~([^\~]*)\~/i', '/\_([^\_]*)\_/i', '/\ \-(\S[^\-]*\S)\-\ /i'], ['$1', '$1', '$1', ' $1 '], $comment1 ); $comment1 = preg_replace_callback( '/\[f:([0-9]+):([0-9]+)(:[0-9]+)?:([^\]]+)\]/', static function (array $m): string { $url = htmlspecialchars(lp('/forum/' . $m[1] . '/' . $m[2] . ($m[3] ?? '') . '/'), ENT_QUOTES, 'UTF-8'); return '' . htmlspecialchars($m[4], ENT_QUOTES, 'UTF-8') . ''; }, $comment1 ); $comment1 = preg_replace_callback('/book:([0-9]*)/i', 'Forum_replacer_link', $comment1); $val = <<
END; $is_author_hidden = 0; if (($Row['written_by'] == $Row['user_id']) && ($Row['finished'] == 0) && ($Row['bookauthor'] == 4)) { $is_author_hidden = 1; $val .= <<<'END' ~ (автор скрыт) END; } else { $buyit = ''; $buyreason = 'Не покупал эту главу, и даже не открывал эту главу (если это первая часть или автор)'; if ($Row['paid'] > 0) { $buyit = ''; $buyreason = 'Купил эту главу за деньги'; } if ($Row['paid'] == 0) { $buyit = ''; $buyreason = 'Читает по абонементу, это бесплатная глава или это сам автор'; } $seoname = '-' . SeoBook($Row['name']) . '-' . SeoBook($Row['realname']); $urname2 = SeoAuthor($Row['realname']); $buyIndicator = ''; if ($buyit !== '') { $buyIndicator = '' . $buyit . ''; } $realname_safe = $esc($Row['realname']); $pic_safe = preg_replace('/[^0-9]/', '', (string) ($Row['pic'] ?? '')); $nxtImg = $pic_safe !== '' ? "{$realname_safe}" : ''; $btn_delete = ''; if ((in_array($id, $z_admins, true)) || ($id == $Row['written_by'])) { $btn_delete .= <<
END; } $units = 'мин.'; $ago = 'назад'; $when = $Row['inminutes']; if ($when > 59) { $units = 'час.'; $when = (int) (($when + 30) / 60); if ($when > 23) { $units = 'дн.'; $when = (int) (($when + 12) / 24); } } if ($when == 0) { $when = 'сейчас'; $units = ''; $ago = ''; } $comment_added_formatted = $formatBookDateTime($Row['added']); $lastupdate = "$when $units $ago"; $commentUserHref = htmlspecialchars(lp('/user/' . $Row['user_id'] . '/'), ENT_QUOTES, 'UTF-8'); $val .= << $nxtImg
{$realname_safe} {$buyIndicator}
$lastupdate
{$edit_comment_btn} {$editmycomment}
$btn_delete
END; } $reducedcomm = ''; $fbcommlen = mb_strlen($comment1); if ($fbcommlen > 350) { $reducedcomm = " END; } else { $comment1 = preg_replace('/:sm([0-9]{2})/', ":sm$1", $comment1); //smiles $comment1 = preg_replace('/:jm(1?[0-9]{2})/', ":jm$1", $comment1); //smiles $reducedcomm = $comment1; } $val .= <<
$reducedcomm
END; $hiskarma = $Row['karma']; if ($hiskarma < 0) { $hiskarma = 0; } $lastupdateadded2 = str_replace(' ', 'T', $Row['added2']); $karmaline = ''; // if ($is_author_hidden == 0) // $karmaline = <<$hiskarma // END; if ($is_author_hidden == 0) { $answer_url_og = "id:$Row[uid]"; } else { // Для скрытого автора — подставьте 0 или специальное значение $answer_url_og = 'id:0'; } if ((int) $id > 0) { $commentInteractionBlock = <<
$karmaline
Ответить
END; } else { $loginHref = htmlspecialchars(lp('/login/'), ENT_QUOTES, 'UTF-8'); $commentInteractionBlock = << $karmaline
END; } $val .= $commentInteractionBlock; if ($canManageComment) { $nonce_compl = create_nonce('ajax_complaint'); $val .= <<