1: <?php
2: /**
3: * XOOPS comment renderer
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package kernel
15: * @subpackage comment
16: * @since 2.0.0
17: * @author Kazumi Ono <onokazu@xoops.org>
18: */
19:
20: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
21:
22: /**
23: * Display comments
24: *
25: * @author Kazumi Ono <onokazu@xoops.org>
26: * @access public
27: */
28: class XoopsCommentRenderer
29: {
30: /**
31: * *#@+
32: *
33: * @access private
34: */
35: public $_tpl;
36: public $_comments;
37: public $_useIcons = true;
38: public $_doIconCheck = false;
39: /**
40: * @var false|\XoopsMemberHandler
41: */
42: public $_memberHandler;
43: public $_statusText;
44: /**
45: * *#@-
46: */
47:
48: /**
49: * Constructor
50: *
51: * @param XoopsTpl $tpl
52: * @param boolean $use_icons
53: * @param boolean $do_iconcheck
54: *
55: */
56: public function __construct(XoopsTpl $tpl, $use_icons = true, $do_iconcheck = false)
57: {
58: $this->_tpl = $tpl;
59: $this->_useIcons = (bool)$use_icons;
60: $this->_doIconCheck = (bool)$do_iconcheck;
61: $this->_memberHandler = xoops_getHandler('member');
62: $this->_statusText = array(
63: XOOPS_COMMENT_PENDING => '<span style="text-decoration: none; font-weight: bold; color: #00ff00;">' . _CM_PENDING . '</span>',
64: XOOPS_COMMENT_ACTIVE => '<span style="text-decoration: none; font-weight: bold; color: #ff0000;">' . _CM_ACTIVE . '</span>',
65: XOOPS_COMMENT_HIDDEN => '<span style="text-decoration: none; font-weight: bold; color: #0000ff;">' . _CM_HIDDEN . '</span>');
66: }
67:
68: /**
69: * Access the only instance of this class
70: *
71: * @param XoopsTpl $tpl reference to a {@link Smarty} object
72: * @param boolean $use_icons
73: * @param boolean $do_iconcheck
74: * @return \XoopsCommentRenderer
75: */
76: public static function instance(XoopsTpl $tpl, $use_icons = true, $do_iconcheck = false)
77: {
78: static $instance;
79: if (!isset($instance)) {
80: $instance = new XoopsCommentRenderer($tpl, $use_icons, $do_iconcheck);
81: }
82:
83: return $instance;
84: }
85:
86: /**
87: * Accessor
88: *
89: * @param array $comments_arr array of {@link XoopsComment} objects
90: */
91: public function setComments(&$comments_arr)
92: {
93: if (isset($this->_comments)) {
94: unset($this->_comments);
95: }
96: $this->_comments =& $comments_arr;
97: }
98:
99: /**
100: * Render the comments in flat view
101: *
102: * @param boolean $admin_view
103: */
104: public function renderFlatView($admin_view = false)
105: {
106: $count = count($this->_comments);
107: for ($i = 0; $i < $count; ++$i) {
108: if (false !== $this->_useIcons) {
109: $title = $this->_getTitleIcon($this->_comments[$i]->getVar('com_icon')) . '&nbsp;' . $this->_comments[$i]->getVar('com_title');
110: } else {
111: $title = $this->_comments[$i]->getVar('com_title');
112: }
113: // Start edit by voltan
114: $poster = $this->_getPosterArray($this->_comments[$i]->getVar('com_uid'), $this->_comments[$i]->getVar('com_user'), $this->_comments[$i]->getVar('com_url'));
115: if (false !== (bool)$admin_view) {
116: $com_email = $this->_comments[$i]->getVar('com_email');
117: $text = $this->_comments[$i]->getVar('com_text');
118: $text .= '<div style="text-align:right; margin-top: 2px; margin-bottom: 0; margin-right: 2px;">';
119: $text .= _CM_STATUS . ': ' . $this->_statusText[$this->_comments[$i]->getVar('com_status')] . '<br>';
120: $text .= 'IP: <span style="font-weight: bold;">' . $this->_comments[$i]->getVar('com_ip') . '</span>';
121: if (!empty($com_email)) {
122: $text .= '<br>' . _CM_EMAIL . ' :<span style="font-weight: bold;"><a href="mailto:' . $com_email . '" title="' . $com_email . '">' . $com_email . '</a></span>';
123: }
124: $text .= '</div>';
125: } else {
126: // hide comments that are not active
127: if (XOOPS_COMMENT_ACTIVE != $this->_comments[$i]->getVar('com_status')) {
128: continue;
129: } else {
130: $text = $this->_comments[$i]->getVar('com_text');
131: }
132: }
133: // End edit by voltan
134: $this->_tpl->append('comments', array(
135: 'id' => $this->_comments[$i]->getVar('com_id'),
136: 'title' => $title,
137: 'text' => $text,
138: 'date_posted' => formatTimestamp($this->_comments[$i]->getVar('com_created'), 'm'),
139: 'date_modified' => formatTimestamp($this->_comments[$i]->getVar('com_modified'), 'm'),
140: 'poster' => $poster));
141: }
142: }
143:
144: /**
145: * Render the comments in thread view
146: *
147: * This method calls itself recursively
148: *
149: * @param integer $comment_id Should be "0" when called by client
150: * @param boolean $admin_view
151: * @param boolean $show_nav
152: * @return void
153: */
154: public function renderThreadView($comment_id = 0, $admin_view = false, $show_nav = true)
155: {
156: include_once $GLOBALS['xoops']->path('class/tree.php');
157: // construct comment tree
158: $xot = new XoopsObjectTree($this->_comments, 'com_id', 'com_pid', 'com_rootid');
159: $tree =& $xot->getTree();
160:
161: if (false !== $this->_useIcons) {
162: $title = $this->_getTitleIcon($tree[$comment_id]['obj']->getVar('com_icon')) . '&nbsp;' . $tree[$comment_id]['obj']->getVar('com_title');
163: } else {
164: $title = $tree[$comment_id]['obj']->getVar('com_title');
165: }
166: if (false !== (bool)$show_nav && $tree[$comment_id]['obj']->getVar('com_pid') != 0) {
167: $this->_tpl->assign('lang_top', _CM_TOP);
168: $this->_tpl->assign('lang_parent', _CM_PARENT);
169: $this->_tpl->assign('show_threadnav', true);
170: } else {
171: $this->_tpl->assign('show_threadnav', false);
172: }
173: $admin_view = (bool)$admin_view;
174: if (false !== $admin_view) {
175: // admins can see all
176: $com_email = $tree[$comment_id]['obj']->getVar('com_email');
177: $text = $tree[$comment_id]['obj']->getVar('com_text');
178: $text .= '<div style="text-align:right; margin-top: 2px; margin-bottom: 0; margin-right: 2px;">';
179: $text .= _CM_STATUS . ': ' . $this->_statusText[$tree[$comment_id]['obj']->getVar('com_status')] . '<br>';
180: $text .= 'IP: <span style="font-weight: bold;">' . $tree[$comment_id]['obj']->getVar('com_ip') . '</span>';
181: if (!empty($com_email)) {
182: $text .= '<br>' . _CM_EMAIL . ' :<span style="font-weight: bold;"><a href="mailto:' . $com_email . '" title="' . $com_email . '">' . $com_email . '</a></span>';
183: }
184: $text .= '</div>';
185: } else {
186: // hide comments that are not active
187: if (XOOPS_COMMENT_ACTIVE != $tree[$comment_id]['obj']->getVar('com_status')) {
188: // if there are any child comments, display them as root comments
189: if (isset($tree[$comment_id]['child']) && !empty($tree[$comment_id]['child'])) {
190: foreach ($tree[$comment_id]['child'] as $child_id) {
191: $this->renderThreadView($child_id, $admin_view, false);
192: }
193: }
194:
195: return null;
196: } else {
197: $text = $tree[$comment_id]['obj']->getVar('com_text');
198: }
199: }
200: $replies = array();
201: $this->_renderThreadReplies($tree, $comment_id, $replies, '&nbsp;&nbsp;', $admin_view);
202: $show_replies = (count($replies) > 0);// ? true : false;
203: // Start edit by voltan
204: $this->_tpl->append('comments', array(
205: 'pid' => $tree[$comment_id]['obj']->getVar('com_pid'),
206: 'id' => $tree[$comment_id]['obj']->getVar('com_id'),
207: 'itemid' => $tree[$comment_id]['obj']->getVar('com_itemid'),
208: 'rootid' => $tree[$comment_id]['obj']->getVar('com_rootid'),
209: 'title' => $title,
210: 'text' => $text,
211: 'date_posted' => formatTimestamp($tree[$comment_id]['obj']->getVar('com_created'), 'm'),
212: 'date_modified' => formatTimestamp($tree[$comment_id]['obj']->getVar('com_modified'), 'm'),
213: 'poster' => $this->_getPosterArray($tree[$comment_id]['obj']->getVar('com_uid'), $tree[$comment_id]['obj']->getVar('com_user'), $tree[$comment_id]['obj']->getVar('com_url')),
214: 'replies' => $replies,
215: 'show_replies' => $show_replies));
216: // End edit by voltan
217: }
218:
219: /**
220: * Render replies to a thread
221: *
222: * @param array $thread
223: * @param int $key
224: * @param array $replies
225: * @param string $prefix
226: * @param bool $admin_view
227: * @param integer $depth
228: * @param string $current_prefix
229: * @access private
230: */
231: public function _renderThreadReplies(&$thread, $key, &$replies, $prefix, $admin_view, $depth = 0, $current_prefix = '')
232: {
233: $admin_view = (bool)$admin_view;
234: if ($depth > 0) {
235: if (false !== $this->_useIcons) {
236: $title = $this->_getTitleIcon($thread[$key]['obj']->getVar('com_icon')) . '&nbsp;' . $thread[$key]['obj']->getVar('com_title');
237: } else {
238: $title = $thread[$key]['obj']->getVar('com_title');
239: }
240: $title = (false !== $admin_view) ? $title . ' ' . $this->_statusText[$thread[$key]['obj']->getVar('com_status')] : $title;
241: // Start edit by voltan
242: $replies[] = array(
243: 'id' => $key,
244: 'prefix' => $current_prefix,
245: 'date_posted' => formatTimestamp($thread[$key]['obj']->getVar('com_created'), 'm'),
246: 'title' => $title,
247: 'root_id' => $thread[$key]['obj']->getVar('com_rootid'),
248: 'status' => $this->_statusText[$thread[$key]['obj']->getVar('com_status')],
249: 'poster' => $this->_getPosterName($thread[$key]['obj']->getVar('com_uid'), $thread[$key]['obj']->getVar('com_user'), $thread[$key]['obj']->getVar('com_url')));
250: // End edit by voltan
251: $current_prefix .= $prefix;
252: }
253: if (isset($thread[$key]['child']) && !empty($thread[$key]['child'])) {
254: ++$depth;
255: foreach ($thread[$key]['child'] as $childkey) {
256: if (!$admin_view && $thread[$childkey]['obj']->getVar('com_status') != XOOPS_COMMENT_ACTIVE) {
257: // skip this comment if it is not active and continue on processing its child comments instead
258: if (isset($thread[$childkey]['child']) && !empty($thread[$childkey]['child'])) {
259: foreach ($thread[$childkey]['child'] as $childchildkey) {
260: $this->_renderThreadReplies($thread, $childchildkey, $replies, $prefix, $admin_view, $depth);
261: }
262: }
263: } else {
264: $this->_renderThreadReplies($thread, $childkey, $replies, $prefix, $admin_view, $depth, $current_prefix);
265: }
266: }
267: }
268: }
269:
270: /**
271: * Render comments in nested view
272: *
273: * Danger: Recursive!
274: *
275: * @param integer $comment_id Always "0" when called by client.
276: * @param boolean $admin_view
277: * @return void
278: */
279: public function renderNestView($comment_id = 0, $admin_view = false)
280: {
281: include_once $GLOBALS['xoops']->path('class/tree.php');
282: $xot = new XoopsObjectTree($this->_comments, 'com_id', 'com_pid', 'com_rootid');
283: $tree =& $xot->getTree();
284: if (false !== $this->_useIcons) {
285: $title = $this->_getTitleIcon($tree[$comment_id]['obj']->getVar('com_icon')) . '&nbsp;' . $tree[$comment_id]['obj']->getVar('com_title');
286: } else {
287: $title = $tree[$comment_id]['obj']->getVar('com_title');
288: }
289: $admin_view = (bool)$admin_view;
290: if (false !== $admin_view) {
291: $com_email = $tree[$comment_id]['obj']->getVar('com_email');
292: $text = $tree[$comment_id]['obj']->getVar('com_text');
293: $text .= '<div style="text-align:right; margin-top: 2px; margin-bottom: 0; margin-right: 2px;">';
294: $text .= _CM_STATUS . ': ' . $this->_statusText[$tree[$comment_id]['obj']->getVar('com_status')] . '<br>';
295: $text .= 'IP: <span style="font-weight: bold;">' . $tree[$comment_id]['obj']->getVar('com_ip') . '</span>';
296: if (!empty($com_email)) {
297: $text .= '<br>' . _CM_EMAIL . ' :<span style="font-weight: bold;"><a href="mailto:' . $com_email . '" title="' . $com_email . '">' . $com_email . '</a></span>';
298: }
299: $text .= '</div>';
300: } else {
301: // skip this comment if it is not active and continue on processing its child comments instead
302: if (XOOPS_COMMENT_ACTIVE != $tree[$comment_id]['obj']->getVar('com_status')) {
303: // if there are any child comments, display them as root comments
304: if (isset($tree[$comment_id]['child']) && !empty($tree[$comment_id]['child'])) {
305: foreach ($tree[$comment_id]['child'] as $child_id) {
306: $this->renderNestView($child_id, $admin_view);
307: }
308: }
309:
310: return null;
311: } else {
312: $text = $tree[$comment_id]['obj']->getVar('com_text');
313: }
314: }
315: $replies = array();
316: $this->_renderNestReplies($tree, $comment_id, $replies, 25, $admin_view);
317: // Start edit by voltan
318: $this->_tpl->append('comments', array(
319: 'pid' => $tree[$comment_id]['obj']->getVar('com_pid'),
320: 'id' => $tree[$comment_id]['obj']->getVar('com_id'),
321: 'itemid' => $tree[$comment_id]['obj']->getVar('com_itemid'),
322: 'rootid' => $tree[$comment_id]['obj']->getVar('com_rootid'),
323: 'title' => $title,
324: 'text' => $text,
325: 'date_posted' => formatTimestamp($tree[$comment_id]['obj']->getVar('com_created'), 'm'),
326: 'date_modified' => formatTimestamp($tree[$comment_id]['obj']->getVar('com_modified'), 'm'),
327: 'poster' => $this->_getPosterArray($tree[$comment_id]['obj']->getVar('com_uid'), $tree[$comment_id]['obj']->getVar('com_user'), $tree[$comment_id]['obj']->getVar('com_url')),
328: 'replies' => $replies));
329: // End edit by voltan
330: }
331:
332: /**
333: * Render replies in nested view
334: *
335: * @param array $thread
336: * @param int $key
337: * @param array $replies
338: * @param string|int $prefix
339: * @param bool $admin_view
340: * @param integer $depth
341: * @access private
342: */
343: public function _renderNestReplies(&$thread, $key, &$replies, $prefix, $admin_view, $depth = 0)
344: {
345: if ($depth > 0) {
346: if (false !== $this->_useIcons) {
347: $title = $this->_getTitleIcon($thread[$key]['obj']->getVar('com_icon')) . '&nbsp;' . $thread[$key]['obj']->getVar('com_title');
348: } else {
349: $title = $thread[$key]['obj']->getVar('com_title');
350: }
351: $admin_view = (bool)$admin_view;
352: $text = (false !== $admin_view) ? $thread[$key]['obj']->getVar('com_text') . '<div style="text-align:right; margin-top: 2px; margin-right: 2px;">' . _CM_STATUS . ': ' . $this->_statusText[$thread[$key]['obj']->getVar('com_status')] . '<br>IP: <span style="font-weight: bold;">' . $thread[$key]['obj']->getVar('com_ip') . '</span><br>' . _CM_EMAIL . ' :<span style="font-weight: bold;">' . $thread[$key]['obj']->getVar('com_email') . '</span></div>' : $thread[$key]['obj']->getVar('com_text');
353: // Start edit by voltan
354: $replies[] = array(
355: 'id' => $key,
356: 'prefix' => $prefix,
357: 'pid' => $thread[$key]['obj']->getVar('com_pid'),
358: 'itemid' => $thread[$key]['obj']->getVar('com_itemid'),
359: 'rootid' => $thread[$key]['obj']->getVar('com_rootid'),
360: 'title' => $title,
361: 'text' => $text,
362: 'date_posted' => formatTimestamp($thread[$key]['obj']->getVar('com_created'), 'm'),
363: 'date_modified' => formatTimestamp($thread[$key]['obj']->getVar('com_modified'), 'm'),
364: 'poster' => $this->_getPosterArray($thread[$key]['obj']->getVar('com_uid'), $thread[$key]['obj']->getVar('com_user'), $thread[$key]['obj']->getVar('com_url')));
365: // End edit by voltan
366: $prefix += 25;
367: }
368: if (isset($thread[$key]['child']) && !empty($thread[$key]['child'])) {
369: ++$depth;
370: foreach ($thread[$key]['child'] as $childkey) {
371: if (!$admin_view && $thread[$childkey]['obj']->getVar('com_status') != XOOPS_COMMENT_ACTIVE) {
372: // skip this comment if it is not active and continue on processing its child comments instead
373: if (isset($thread[$childkey]['child']) && !empty($thread[$childkey]['child'])) {
374: foreach ($thread[$childkey]['child'] as $childchildkey) {
375: $this->_renderNestReplies($thread, $childchildkey, $replies, $prefix, $admin_view, $depth);
376: }
377: }
378: } else {
379: $this->_renderNestReplies($thread, $childkey, $replies, $prefix, $admin_view, $depth);
380: }
381: }
382: }
383: }
384:
385: /**
386: * Get the name of the poster
387: *
388: * @param int $poster_id
389: * @param $poster_user
390: * @param $poster_website
391: * @return string
392: * @access private
393: */
394: // Start edit by voltan
395: public function _getPosterName($poster_id, $poster_user, $poster_website)
396: {
397: $poster['id'] = (int)$poster_id;
398: if ($poster['id'] > 0) {
399: $com_poster = $this->_memberHandler->getUser($poster_id);
400: if (is_object($com_poster)) {
401: $poster['uname'] = '<a href="' . XOOPS_URL . '/userinfo.php?uid=' . $poster['id'] . '">' . $com_poster->getVar('uname') . '</a>';
402: }
403: } elseif ($poster['id'] == 0 && $poster_user != '') {
404: $poster['id'] = 0; // to cope with deleted user accounts
405: if (!empty($poster_website)) {
406: $poster['uname'] = '<a href="' . $poster_website . '">' . $poster_user . '</a>';
407: } else {
408: $poster['uname'] = $poster_user;
409: }
410: } else {
411: $poster['id'] = 0; // to cope with deleted user accounts
412: $poster['uname'] = $GLOBALS['xoopsConfig']['anonymous'];
413: }
414:
415: return $poster;
416: }
417: // End edit by voltan
418:
419: /**
420: * Get an array with info about the poster
421: *
422: * @param int $poster_id
423: * @param $poster_user
424: * @param $poster_website
425: * @return array
426: * @access private
427: */
428: // Start edit by voltan
429: public function _getPosterArray($poster_id, $poster_user, $poster_website)
430: {
431: $poster['id'] = (int)$poster_id;
432: if ($poster['id'] > 0) {
433: /** @var XoopsUser $com_poster */
434: $com_poster = $this->_memberHandler->getUser($poster['id']);
435: if (is_object($com_poster)) {
436: $poster['uname'] = '<a href="' . XOOPS_URL . '/userinfo.php?uid=' . $poster['id'] . '">' . $com_poster->getVar('uname') . '</a>';
437: $poster_rank = $com_poster->rank();
438: $poster['rank_image'] = ($poster_rank['image'] != '') ? $poster_rank['image'] : 'blank.gif';
439: $poster['rank_title'] = $poster_rank['title'];
440: $poster['avatar'] = $com_poster->getVar('user_avatar');
441: $poster['regdate'] = formatTimestamp($com_poster->getVar('user_regdate'), 's');
442: $poster['from'] = $com_poster->getVar('user_from');
443: $poster['postnum'] = $com_poster->getVar('posts');
444: $poster['status'] = $com_poster->isOnline() ? _CM_ONLINE : '';
445: }
446: } elseif ($poster['id'] == 0 && $poster_user != '') {
447: if (!empty($poster_website)) {
448: $poster['uname'] = '<a href="' . $poster_website . '">' . $poster_user . '</a>';
449: } else {
450: $poster['uname'] = $poster_user;
451: }
452: $poster['id'] = 0; // to cope with deleted user accounts
453: $poster['rank_title'] = '';
454: $poster['avatar'] = 'blank.gif';
455: $poster['regdate'] = '';
456: $poster['from'] = '';
457: $poster['postnum'] = 0;
458: $poster['status'] = '';
459: } else {
460: $poster['uname'] = $GLOBALS['xoopsConfig']['anonymous'];
461: $poster['id'] = 0; // to cope with deleted user accounts
462: $poster['rank_title'] = '';
463: $poster['avatar'] = 'blank.gif';
464: $poster['regdate'] = '';
465: $poster['from'] = '';
466: $poster['postnum'] = 0;
467: $poster['status'] = '';
468: }
469:
470: return $poster;
471: }
472: // End edit by voltan
473:
474: /**
475: * Get the IMG tag for the title icon
476: *
477: * @param string $icon_image
478: * @return string HTML IMG tag
479: * @access private
480: */
481: public function _getTitleIcon($icon_image)
482: {
483: $icon_image = htmlspecialchars(trim($icon_image), ENT_QUOTES);
484: if ($icon_image != '') {
485: if (false !== $this->_doIconCheck) {
486: if (!file_exists($GLOBALS['xoops']->path('images/subject/' . $icon_image))) {
487: return '<img src="' . XOOPS_URL . '/images/icons/no_posticon.gif" alt="" />';
488: } else {
489: return '<img src="' . XOOPS_URL . '/images/subject/' . $icon_image . '" alt="" />';
490: }
491: } else {
492: return '<img src="' . XOOPS_URL . '/images/subject/' . $icon_image . '" alt="" />';
493: }
494: }
495:
496: return '<img src="' . XOOPS_URL . '/images/icons/no_posticon.gif" alt="" />';
497: }
498: }
499: