@@ -1276,18 +1276,25 @@ function getPosts(
12761276{
12771277 $ em = Database::getManager ();
12781278
1279+ $ orderDirection = strtoupper ($ orderDirection );
1280+ if (!in_array ($ orderDirection , ['ASC ' , 'DESC ' ], true )) {
1281+ $ orderDirection = 'ASC ' ;
1282+ }
1283+
1284+ // Build visibility criteria based on permissions
12791285 if (api_is_allowed_to_edit (false , true )) {
1286+ // Entity maps 'visible' as boolean; keeping legacy behavior as requested.
12801287 $ visibleCriteria = Criteria::expr ()->neq ('visible ' , 2 );
12811288 } else {
12821289 $ visibleCriteria = Criteria::expr ()->eq ('visible ' , 1 );
12831290 }
12841291
1292+ $ threadRef = $ em ->getReference (CForumThread::class, $ threadId );
1293+
12851294 $ criteria = Criteria::create ();
12861295 $ criteria
1287- ->where (Criteria::expr ()->eq ('thread ' , $ threadId ))
1288- //->andWhere(Criteria::expr()->eq('cId', $forum->getCId()))
1289- ->andWhere ($ visibleCriteria )
1290- ;
1296+ ->where (Criteria::expr ()->eq ('thread ' , $ threadRef ))
1297+ ->andWhere ($ visibleCriteria );
12911298
12921299 $ groupId = api_get_group_id ();
12931300 $ filterModerated = true ;
@@ -1306,24 +1313,33 @@ function getPosts(
13061313 }
13071314
13081315 if ($ recursive ) {
1309- $ criteria ->andWhere (Criteria::expr ()->eq ('postParent ' , $ postId ));
1316+ // Compare association with entity reference when filtering by parent
1317+ $ parentRef = $ postId ? $ em ->getReference (CForumPost::class, $ postId ) : null ;
1318+ $ criteria ->andWhere (Criteria::expr ()->eq ('postParent ' , $ parentRef ));
13101319 }
13111320
13121321 $ qb = $ em ->getRepository (CForumPost::class)->createQueryBuilder ('p ' );
13131322 $ qb ->select ('p ' )
13141323 ->addCriteria ($ criteria )
13151324 ->addOrderBy ('p.iid ' , $ orderDirection );
13161325
1326+ // Apply moderation filter if forum is moderated and user is not editor
13171327 if ($ filterModerated && 1 == $ forum ->isModerated ()) {
13181328 if (!api_is_allowed_to_edit (false , true )) {
13191329 $ userId = api_get_user_id ();
1330+
1331+ // instead of a non-existent scalar field like p.posterId
13201332 $ qb ->andWhere (
1321- 'p.status = 1 OR
1322- (p.status = ' .CForumPost::STATUS_WAITING_MODERATION ." AND p.posterId = $ userId) OR
1323- (p.status = " .CForumPost::STATUS_REJECTED ." AND p.posterId = $ userId) OR
1324- (p.status IS NULL AND p.posterId = $ userId)
1325- "
1326- );
1333+ '(p.status = :st_valid)
1334+ OR (p.status IN (:st_own) AND IDENTITY(p.user) = :uid)
1335+ OR (p.status IS NULL AND IDENTITY(p.user) = :uid) '
1336+ )
1337+ ->setParameter ('st_valid ' , CForumPost::STATUS_VALIDATED )
1338+ ->setParameter ('st_own ' , [
1339+ CForumPost::STATUS_WAITING_MODERATION ,
1340+ CForumPost::STATUS_REJECTED ,
1341+ ])
1342+ ->setParameter ('uid ' , $ userId );
13271343 }
13281344 }
13291345
@@ -1340,8 +1356,6 @@ function getPosts(
13401356 'post_text ' => $ post ->getPostText (),
13411357 'thread_id ' => $ post ->getThread () ? $ post ->getThread ()->getIid () : 0 ,
13421358 'forum_id ' => $ post ->getForum ()->getIid (),
1343- //'poster_id' => $post->getPosterId(),
1344- //'poster_name' => $post->getPosterName(),
13451359 'post_date ' => $ post ->getPostDate (),
13461360 'post_notification ' => $ post ->getPostNotification (),
13471361 'post_parent_id ' => $ post ->getPostParent () ? $ post ->getPostParent ()->getIid () : 0 ,
@@ -1351,6 +1365,7 @@ function getPosts(
13511365 'entity ' => $ post ,
13521366 ];
13531367
1368+ // Fill user info if available
13541369 $ user = $ post ->getUser ();
13551370 if ($ user ) {
13561371 $ postInfo ['user_id ' ] = $ user ->getId ();
@@ -1366,6 +1381,8 @@ function getPosts(
13661381 if (!$ recursive ) {
13671382 continue ;
13681383 }
1384+
1385+ // Recursive fetch of children when requested
13691386 $ list = array_merge (
13701387 $ list ,
13711388 getPosts (
@@ -2005,17 +2022,6 @@ function show_add_post_form(CForum $forum, CForumThread $thread, CForumPost $pos
20052022 );
20062023 }
20072024
2008- $ iframe = null ;
2009- if ($ showPreview ) {
2010- if ('newthread ' !== $ action && !empty ($ threadId )) {
2011- $ iframe = '<iframe style="border: 1px solid black"
2012- src=" ' .api_get_path (WEB_CODE_PATH ).'forum/iframe_thread.php? ' .api_get_cidreq ().'&forum= ' .$ forumId .'&thread= ' .$ threadId .'# ' .$ postId .'" width="100%"></iframe> ' ;
2013- }
2014- if (!empty ($ iframe )) {
2015- $ form ->addElement ('label ' , get_lang ('Thread ' ), $ iframe );
2016- }
2017- }
2018-
20192025 if (in_array ($ action , ['quote ' , 'replymessage ' ])) {
20202026 $ form ->addFile ('user_upload[] ' , get_lang ('Attachment ' ));
20212027 $ form ->addButton (
@@ -2110,6 +2116,11 @@ function show_add_post_form(CForum $forum, CForumThread $thread, CForumPost $pos
21102116 );
21112117 }
21122118
2119+ if ($ showPreview && 'newthread ' !== $ action && !empty ($ threadId )) {
2120+ $ previewHtml = render_thread_preview_html ((int ) $ forumId , (int ) $ threadId , (int ) $ postId , 20 );
2121+ $ form ->addElement ('html ' , '<div class="mt-4"> ' .$ previewHtml .'</div> ' );
2122+ }
2123+
21132124 // Validation or display
21142125 if ($ form ->validate ()) {
21152126 $ check = Security::check_token ('post ' );
@@ -2202,6 +2213,102 @@ function show_add_post_form(CForum $forum, CForumThread $thread, CForumPost $pos
22022213 }
22032214}
22042215
2216+ /**
2217+ * Build a lightweight Tailwind-based preview for a thread (no iframe).
2218+ * Security: keep XSS protection as in the original iframe.
2219+ */
2220+ function render_thread_preview_html (
2221+ int $ forumId ,
2222+ int $ threadId ,
2223+ int $ highlightPostId = 0 ,
2224+ int $ limit = 20 ,
2225+ string $ heightClass = 'h-72 '
2226+ ): string {
2227+ $ tablePosts = Database::get_course_table (TABLE_FORUM_POST ); // c_forum_post
2228+ $ tableUsers = Database::get_main_table (TABLE_MAIN_USER ); // user
2229+
2230+ $ sql = "SELECT
2231+ p.iid,
2232+ p.post_date,
2233+ p.title AS post_title,
2234+ p.post_text,
2235+ u.id AS user_id,
2236+ u.username,
2237+ u.firstname,
2238+ u.lastname
2239+ FROM $ tablePosts p
2240+ INNER JOIN $ tableUsers u ON u.id = p.poster_id
2241+ WHERE p.thread_id = " .(int )$ threadId ."
2242+ AND p.visible = 1
2243+ AND (p.status IS NULL OR p.status = " .(int ) CForumPost::STATUS_VALIDATED .")
2244+ ORDER BY p.post_date ASC
2245+ LIMIT " .(int )$ limit ;
2246+
2247+ $ res = Database::query ($ sql );
2248+ $ rows = [];
2249+ while ($ r = Database::fetch_array ($ res )) {
2250+ $ rows [] = $ r ;
2251+ }
2252+
2253+ ob_start (); ?>
2254+ <div class="mt-6 w-full">
2255+ <div
2256+ class="forum-preview <?php echo $ heightClass ?> overflow-y-auto border border-gray-25 rounded-xl p-3 bg-white"
2257+ role="region"
2258+ aria-label="<?php echo get_lang ('Discussion thread ' ); ?> "
2259+ >
2260+ <div class="sticky top-0 z-10 bg-white pb-2 text-gray-90 font-bold shadow-[0_1px_0_0_#e4e9ed]">
2261+ <?php echo get_lang ('Discussion thread ' ); ?>
2262+ </div>
2263+
2264+ <?php if (empty ($ rows )): ?>
2265+ <div class="text-gray-50 text-body-2 mt-2"><?php echo get_lang ('No posts yet ' ); ?> </div>
2266+ <?php else : foreach ($ rows as $ row ):
2267+ $ postId = (int ) $ row ['iid ' ];
2268+ $ isFocus = $ highlightPostId && $ postId === (int ) $ highlightPostId ;
2269+
2270+ // Compute display name (fallback to username)
2271+ $ isAnon = ((int )$ row ['user_id ' ] === (int )\Chamilo \CoreBundle \Entity \User::ANONYMOUS );
2272+ $ displayName = $ isAnon
2273+ ? get_lang ('Anonymous ' )
2274+ : (api_get_person_name ($ row ['firstname ' ], $ row ['lastname ' ]) ?: $ row ['username ' ]);
2275+
2276+ $ userTitle = api_htmlentities (sprintf (get_lang ('Login: %s ' ), $ row ['username ' ]), ENT_QUOTES );
2277+ ?>
2278+ <article id="post-<?php echo $ postId ?> "
2279+ class="mb-2 border border-gray-25 rounded-lg p-3 <?php echo $ isFocus ? 'bg-gray-15 ring-1 ring-primary ' : 'bg-white ' ?> ">
2280+ <div class="text-caption text-gray-50" title="<?php echo $ userTitle ?> ">
2281+ <?php echo Security::remove_XSS ($ displayName ) ?> • <?php echo api_convert_and_format_date ($ row ['post_date ' ]) ?>
2282+ </div>
2283+
2284+ <div class="font-bold mt-1 mb-1 text-gray-90">
2285+ <?php echo Security::remove_XSS ($ row ['post_title ' ]) ?>
2286+ </div>
2287+
2288+ <div class="text-body-2 leading-4">
2289+ <?php echo Security::remove_XSS ($ row ['post_text ' ], STUDENT ) ?>
2290+ </div>
2291+ </article>
2292+ <?php endforeach ; endif ; ?>
2293+ </div>
2294+
2295+ <div class="mt-2">
2296+ <a class="text-body-2 underline text-primary"
2297+ href="<?php echo api_get_path (WEB_CODE_PATH ) . 'forum/viewthread.php? ' . api_get_cidreq ()
2298+ . '&forum= ' .(int )$ forumId .'&thread= ' .(int )$ threadId ?> ">
2299+ <?php echo get_lang ('View full thread ' ) ?>
2300+ </a>
2301+ </div>
2302+ </div>
2303+ <?php
2304+ if ($ highlightPostId ) {
2305+ // Try to bring the highlighted post into view.
2306+ echo '<script>try{document.getElementById("post- ' .$ highlightPostId .'")?.scrollIntoView({behavior:"instant",block:"start"});}catch(e){}</script> ' ;
2307+ }
2308+
2309+ return (string ) ob_get_clean ();
2310+ }
2311+
22052312function newThread (CForum $ forum , $ form_values = '' , $ showPreview = true )
22062313{
22072314 $ _user = api_get_user_info ();
@@ -5167,29 +5274,76 @@ function getCountPostsWithStatus($status, $forum, $threadId = null)
51675274 *
51685275 * @return bool
51695276 */
5170- function postIsEditableByStudent ($ forum , $ post )
5277+ /**
5278+ * Student editability guard that accepts either an entity or the array shape used in views.
5279+ *
5280+ * @param CForum $forum
5281+ * @param CForumPost|array $post
5282+ */
5283+ function postIsEditableByStudent (CForum $ forum , $ post ): bool
51715284{
5285+ // Normalize $post to an entity
5286+ /** @var CForumPost|null $entity */
5287+ $ entity = null ;
5288+
5289+ if ($ post instanceof CForumPost) {
5290+ $ entity = $ post ;
5291+ } elseif (is_array ($ post )) {
5292+ // Prefer the embedded entity from getPosts()
5293+ if (isset ($ post ['entity ' ]) && $ post ['entity ' ] instanceof CForumPost) {
5294+ $ entity = $ post ['entity ' ];
5295+ } else {
5296+ // Fallback: fetch by id if present in the array
5297+ $ postId = $ post ['post_id ' ] ?? $ post ['iid ' ] ?? null ;
5298+ if ($ postId ) {
5299+ $ em = Database::getManager ();
5300+ $ entity = $ em ->getRepository (CForumPost::class)->find ($ postId );
5301+ }
5302+ }
5303+ }
5304+
5305+ // If we still don't have a valid entity, be conservative: deny student edit
5306+ if (!$ entity instanceof CForumPost) {
5307+ return false ;
5308+ }
5309+
5310+ // Admins/teachers can always edit
51725311 if (api_is_platform_admin () || api_is_allowed_to_edit ()) {
51735312 return true ;
51745313 }
51755314
5176- if (1 == $ forum ->isModerated ()) {
5177- if (null === $ post ->getStatus ()) {
5315+ $ isModerated = (bool ) $ forum ->isModerated ();
5316+ $ userId = (int ) api_get_user_id ();
5317+ $ ownerId = $ entity ->getUser () ? (int ) $ entity ->getUser ()->getId () : 0 ;
5318+ $ isOwner = ($ ownerId === $ userId );
5319+ $ status = $ entity ->getStatus ();
5320+
5321+ // Unmoderated forum: student can edit only own posts
5322+ if (!$ isModerated ) {
5323+ return $ isOwner ;
5324+ }
5325+
5326+ // editable by owner when status is NULL or WAITING or REJECTED.
5327+ if ($ isOwner ) {
5328+ if ($ status === null ) {
51785329 return true ;
5179- } else {
5180- return in_array (
5181- $ post ->getStatus (),
5182- [
5183- CForumPost::STATUS_WAITING_MODERATION ,
5184- CForumPost::STATUS_REJECTED ,
5185- ]
5186- );
51875330 }
5331+
5332+ return in_array (
5333+ $ status ,
5334+ [
5335+ CForumPost::STATUS_WAITING_MODERATION ,
5336+ CForumPost::STATUS_REJECTED ,
5337+ ],
5338+ true
5339+ );
51885340 }
51895341
5190- return true ;
5342+ // Not owner and not a teacher/admin
5343+ return false ;
51915344}
51925345
5346+
51935347/**
51945348 * @return bool
51955349 */
0 commit comments