rating = NULL; $this->featured = 0; $this->nofollow = 1; $this->notif_status = 'noreq'; $this->in_reply_to_cmt_ID = 0; $this->set_renderers( array( 'default' ) ); $this->set( 'status', 'draft' ); } else { $this->ID = $db_row->comment_ID; $this->item_ID = $db_row->comment_item_ID; if( ! empty( $db_row->comment_author_user_ID ) ) { $this->author_user_ID = $db_row->comment_author_user_ID; } $this->type = $db_row->comment_type; $this->status = $db_row->comment_status; $this->author = $db_row->comment_author; $this->author_email = $db_row->comment_author_email; $url = trim( $db_row->comment_author_url ); if( ! empty($url) && ! preg_match( '~^\w+://~', $url ) ) { // URL given and does not start with a protocol: $url = 'http://'.$url; } $this->author_url = $url; $this->author_IP = $db_row->comment_author_IP; $this->IP_ctry_ID = $db_row->comment_IP_ctry_ID; $this->date = $db_row->comment_date; $this->last_touched_ts = $db_row->comment_last_touched_ts; $this->content = $db_row->comment_content; $this->renderers = $db_row->comment_renderers; $this->rating = $db_row->comment_rating; $this->featured = $db_row->comment_featured; $this->nofollow = $db_row->comment_nofollow; $this->spam_karma = $db_row->comment_spam_karma; $this->allow_msgform = $db_row->comment_allow_msgform; $this->secret = $db_row->comment_secret; $this->notif_status = $db_row->comment_notif_status; $this->notif_ctsk_ID = $db_row->comment_notif_ctsk_ID; $this->notif_flags = $db_row->comment_notif_flags; $this->in_reply_to_cmt_ID = $db_row->comment_in_reply_to_cmt_ID; $this->helpful_addvotes = $db_row->comment_helpful_addvotes; $this->helpful_countvotes = $db_row->comment_helpful_countvotes; $this->spam_addvotes = $db_row->comment_spam_addvotes; $this->spam_countvotes = $db_row->comment_spam_countvotes; } } /** * Get this class db table config params * * @return array */ static function get_class_db_config() { static $comment_db_config; if( !isset( $comment_db_config ) ) { $comment_db_config = array_merge( parent::get_class_db_config(), array( 'dbtablename' => 'T_comments', 'dbprefix' => 'comment_', 'dbIDname' => 'comment_ID', ) ); } return $comment_db_config; } /** * Get delete cascade settings * * @return array */ static function get_delete_cascades() { return array( array( 'table'=>'T_links', 'fk'=>'link_cmt_ID', 'msg'=>T_('%d links to destination comments'), 'class'=>'Link', 'class_path'=>'links/model/_link.class.php' ), array( 'table'=>'T_comments__votes', 'fk'=>'cmvt_cmt_ID', 'msg'=>T_('%d votes on comment') ), array( 'table'=>'T_comments__prerendering', 'fk'=>'cmpr_cmt_ID', 'msg'=>T_('%d prerendered content') ), ); } /** * Delete those comments from the database which corresponds to the given condition or to the given ids array * Note: the delete cascade arrays are handled! * * @param string the name of this class * Note: This is required until min phpversion will be 5.3. Since PHP 5.3 we can use static::function_name to achieve late static bindings * @param string where condition * @param array object ids * @return mixed # of rows affected or false if error */ static function db_delete_where( $class_name, $sql_where, $object_ids = NULL, $params = NULL ) { global $DB; $use_transaction = ( isset( $params['use_transaction'] ) ) ? $params['use_transaction'] : true; if( $use_transaction ) { $DB->begin(); $params['use_transaction'] = false; } if( ! empty( $sql_where ) ) { $object_ids = $DB->get_col( 'SELECT comment_ID FROM T_comments WHERE '.$sql_where ); } if( ! $object_ids ) { // There is no comment to delete if( $use_transaction ) { // Commit transaction if it was started $DB->commit(); } return; } $query_get_attached_file_ids = 'SELECT link_file_ID FROM T_links WHERE link_cmt_ID IN ( '.implode( ', ', $object_ids ).' )'; $attached_file_ids = $DB->get_col( $query_get_attached_file_ids ); $result = parent::db_delete_where( $class_name, $sql_where, $object_ids ); if( ( $result !== false ) && ( ! empty( $attached_file_ids ) ) ) { // Delete orphan attachments and empty comment attachment folders load_funcs( 'files/model/_file.funcs.php' ); remove_orphan_files( $attached_file_ids, NULL, true ); } if( $use_transaction ) { // Commit or rollback the transaction ( $result !== false ) ? $DB->commit() : $DB->rollback(); } return $result; } /** * Get the author User of the comment. This is NULL for anonymous visitors. * * @return User */ function & get_author_User() { if( isset($this->author_user_ID) && ! isset($this->author_User) ) { $UserCache = & get_UserCache(); $this->author_User = & $UserCache->get_by_ID( $this->author_user_ID ); } return $this->author_User; } /** * Get the Item this comment relates to * * @return Item */ function & get_Item() { if( ! isset($this->Item) ) { $ItemCache = & get_ItemCache(); $this->Item = & $ItemCache->get_by_ID( $this->item_ID, false, false ); } return $this->Item; } /** * Get a member param by its name * * @param mixed Name of parameter * @return mixed Value of parameter */ function get( $parname ) { switch( $parname ) { case 't_status': // Text status: $visibility_statuses = get_visibility_statuses( '', array( 'redirected' ) ); return $visibility_statuses[ $this->status ]; case 'notif_flags': return empty( $this->notif_flags ) ? array() : explode( ',', $this->notif_flags ); } return parent::get( $parname ); } /** * Set param value * * @param string parameter name * @param mixed parameter value * @param boolean true to set to NULL if empty value * @return boolean true, if a value has been set; false if it has not changed */ function set( $parname, $parvalue, $make_null = false ) { switch( $parname ) { case 'rating': // Save star rating with checking correct values if( $parvalue < 1 ) { // cannot be less than 0 $parvalue = NULL; } elseif( $parvalue > 5 ) { // cannot be more than 5 $parvalue = 5; } return $this->set_param( $parname, 'number', $parvalue, true ); case 'author_email': return $this->set_param( $parname, 'string', utf8_strtolower( $parvalue ), $make_null ); case 'notif_flags': $notifications_flags = $this->get( 'notif_flags' ); if( ! is_array( $parvalue ) ) { // Convert string to array: $parvalue = array( $parvalue ); } $notifications_flags = array_merge( $notifications_flags, $parvalue ); $notifications_flags = array_unique( $notifications_flags ); return $this->set_param( 'notif_flags', 'string', implode( ',', $notifications_flags ), $make_null ); case 'status': // We need to set a reminder here to later check if the new status is allowed at dbinsert or dbupdate time ( $this->restrict_status( true ) ) // We cannot check immediately because we may be setting the status before having set a main cat_ID -> a collection ID to check the status possibilities // Save previous status temporarily to make some changes on dbinsert(), dbupdate() & dbdelete() if( ! isset( $this->previous_status ) ) { // Set once previous status to know what status was original on several rewriting per same page request: $this->previous_status = $this->get( 'status' ); } return parent::set( 'status', $parvalue, $make_null ); default: return $this->set_param( $parname, 'string', $parvalue, $make_null ); } } /** * Set Item this comment relates to * @param Item */ function set_Item( & $Item ) { // Save previous item ID temporarily to make some changes on dbupdate() $this->previous_item_ID = $this->item_ID; $this->Item = & $Item; parent::set_param( 'item_ID', 'number', $Item->ID ); } /** * Set author User of this comment */ function set_author_User( & $author_User ) { $this->author_User = & $author_User; parent::set_param( 'author_user_ID', 'number', $author_User->ID ); } /** * Set the spam karma, as a number. * @param integer Spam karma (-100 - 100) * @access protected */ function set_spam_karma( $spam_karma ) { return $this->set_param( 'spam_karma', 'number', $spam_karma ); } /** * Set the vote, as a number. * * @param string Vote type (spam, helpful) * @param string Vote value (spam, notsure, ok, yes, no) * @access protected */ function set_vote( $vote_type, $vote_value ) { global $DB, $current_User; if( ! in_array( $vote_type, array( 'spam', 'helpful' ) ) ) { // Restrict access for bad requests return; } switch ( $vote_value ) { // Set a value for spam vote case 'spam': case 'yes': $vote = '1'; break; case 'notsure': case 'noopinion': $vote = '0'; break; case 'ok': case 'no': $vote = '-1'; break; default: // $vote_value is not correct from ajax request return; } if( empty( $this->ID ) ) { // If comment doesn't exist return; } $DB->begin(); $SQL = new SQL( 'Check if current user already voted on comment #'.$this->ID ); $SQL->SELECT( 'cmvt_cmt_ID, cmvt_'.$vote_type.' AS value' ); $SQL->FROM( 'T_comments__votes' ); $SQL->WHERE( 'cmvt_cmt_ID = '.$DB->quote( $this->ID ) ); $SQL->WHERE_and( 'cmvt_user_ID = '.$DB->quote( $current_User->ID ) ); $existing_vote = $DB->get_row( $SQL->get(), OBJECT, NULL, $SQL->title ); if( $existing_vote === NULL ) { // Add a new vote for first time: // Use a replace into to avoid duplicate key conflict in case when user clicks two times fast one after the other: $DB->query( 'INSERT INTO T_comments__votes ( cmvt_cmt_ID, cmvt_user_ID, cmvt_'.$vote_type.' ) VALUES ( '.$DB->quote( $this->ID ).', '.$DB->quote( $current_User->ID ).', '.$DB->quote( $vote ).' )', 'Add new vote on comment #'.$this->ID ); } else { // Update a vote: if( $existing_vote->value == $vote ) { // Undo previous vote: $vote = NULL; } $DB->query( 'UPDATE T_comments__votes SET cmvt_'.$vote_type.' = '.$DB->quote( $vote ).' WHERE cmvt_cmt_ID = '.$DB->quote( $this->ID ).' AND cmvt_user_ID = '.$DB->quote( $current_User->ID ), 'Update a vote on comment #'.$this->ID ); } $vote_SQL = new SQL( 'Get voting results of comment #'.$this->ID ); $vote_SQL->SELECT( 'COUNT( cmvt_'.$vote_type.' ) AS votes_count, SUM( cmvt_'.$vote_type.' ) AS votes_sum' ); $vote_SQL->FROM( 'T_comments__votes' ); $vote_SQL->WHERE( 'cmvt_cmt_ID = '.$DB->quote( $this->ID ) ); $vote_SQL->WHERE_and( 'cmvt_'.$vote_type.' IS NOT NULL' ); $vote = $DB->get_row( $vote_SQL->get() ); // These values must be number and not NULL: $vote->votes_sum = intval( $vote->votes_sum ); $vote->votes_count = intval( $vote->votes_count ); // Update fields with vote counters for this comment $DB->query( 'UPDATE T_comments SET comment_'.$vote_type.'_addvotes = '.$DB->quote( $vote->votes_sum ).', comment_'.$vote_type.'_countvotes = '.$DB->quote( $vote->votes_count ).' WHERE comment_ID = '.$DB->quote( $this->ID ), 'Update fields with vote counters for comment #'.$this->ID ); $this->{$vote_type.'_addvotes'} = $vote->votes_sum; $this->{$vote_type.'_countvotes'} = $vote->votes_count; $DB->commit(); if( $vote_type == 'spam' && $vote_value == 'spam' ) { // This is a voting about spam comment we should inform moderators: $this->send_vote_spam_emails(); } return; } /** * Get the vote statuses for current user * * @param string Vote type: 'spam', 'helpful' * @return boolean */ function get_vote_status( $type = 'spam' ) { global $current_User, $DB, $cache_comments_vote_statuses; if( ! is_logged_in() ) { // Current user must be logged in: return false; } if( ! is_array( $cache_comments_vote_statuses ) ) { // Initialize array first time: $cache_comments_vote_statuses = array(); } if( ! isset( $cache_comments_vote_statuses[ $this->ID ] ) ) { // Get a vote status from DB and cache in global variable: $SQL = new SQL( 'Get the vote statuses for current user and comment #'.$this->ID ); $SQL->SELECT( 'cmvt_spam AS spam, cmvt_helpful AS helpful' ); $SQL->FROM( 'T_comments__votes' ); $SQL->WHERE( 'cmvt_cmt_ID = '.$DB->quote( $this->ID ) ); $SQL->WHERE_and( 'cmvt_user_ID = '.$DB->quote( $current_User->ID ) ); $cache_comments_vote_statuses[ $this->ID ] = $DB->get_row( $SQL->get(), ARRAY_A, NULL, $SQL->title ); } if( isset( $cache_comments_vote_statuses[ $this->ID ][ $type ] ) ) { // Return a vote status: return $cache_comments_vote_statuses[ $this->ID ][ $type ]; } else { // Current user didn't vote on this comment yet: return false; } } /** * Get the vote spam type disabled, as array. * * @param int User ID * * @return array Result: * 'is_voted' - TRUE if current user already voted on this comment * 'icons_statuses': array( 'spam', 'notsure', 'ok' ) */ function get_vote_spam_disabled() { global $DB, $current_User; $result = array( 'is_voted' => false, 'icons_statuses' => array( 'ok' => 'disabled', 'notsure' => 'disabled', 'spam' => 'disabled', ) ); $vote = $this->get_vote_status( 'spam' ); if( $vote !== false ) { // Get a spam vote for current comment and user: $result['is_voted'] = true; $class_disabled = 'disabled'; $class_voted = 'voted'; switch ( $vote ) { case '1': // SPAM $result['icons_statuses']['spam'] = $class_voted; $result['icons_statuses']['notsure'] = $result['icons_statuses']['ok'] = $class_disabled; break; case '0': // NOT SURE $result['icons_statuses']['notsure'] = $class_voted; $result['icons_statuses']['spam'] = $result['icons_statuses']['ok'] = $class_disabled; break; case '-1': // OK $result['icons_statuses']['ok'] = $class_voted; $result['icons_statuses']['spam'] = $result['icons_statuses']['notsure'] = $class_disabled; break; } } return $result; } /** * Get the vote helpful type disabled, as array. * * @return array Result: * 'is_voted' - TRUE if current user already voted on this comment * 'icons_statuses': array( 'yes', 'no' ) */ function get_vote_helpful_disabled() { global $DB, $current_User; $result = array( 'is_voted' => false, 'icons_statuses' => array( 'yes' => '', 'no' => '' ) ); $vote = $this->get_vote_status( 'helpful' ); if( $vote !== false ) { // Get a helpful vote for current comment and user: $result['is_voted'] = true; $class_disabled = 'disabled'; $class_voted = 'voted'; switch ( $vote ) { case '1': // YES $result['icons_statuses']['yes'] = $class_voted; $result['icons_statuses']['no'] = $class_disabled; break; case '-1': // NO $result['icons_statuses']['no'] = $class_voted; $result['icons_statuses']['yes'] = $class_disabled; break; } } return $result; } /** * Get the vote summary, as a string. * * @param type Vote type (spam, helpful) * @param srray Params * @return string */ function get_vote_summary( $type, $params = array() ) { $params = array_merge( array( 'result_title' => '', 'result_title_undecided' => '', 'after_result' => '', ), $params ); if( ! in_array( $type, array( 'spam', 'helpful' ) ) ) { // Bad request return ''; } if( $this->{$type.'_countvotes'} == 0 ) { // No votes for current comment return ''; } // Calculate a vote summary $summary = ceil( $this->{$type.'_addvotes'} / $this->{$type.'_countvotes'} * 100 ); if( $summary < -20 ) { // Comment is OK $summary = abs($summary).'% '.( $type == 'spam' ? T_('OK') : T_('Negative') ); } else if( $summary >= -20 && $summary <= 20 ) { // Comment is UNDECIDED $summary = T_('UNDECIDED'); if( !empty( $params['result_title_undecided'] ) ) { // Display title before undecided results $summary = $params['result_title_undecided'].' '.$summary; } } else if( $summary > 20 ) { // Comment is SPAM $summary .= '% '.( $type == 'spam' ? T_('SPAM') : T_('Positive') ); } if( !empty( $params['result_title'] ) ) { // Display title before results $summary = $params['result_title'].' '.$summary; } return $summary.$params['after_result'].' '; } /** * Get the anchor-ID of the comment * * @return string */ function get_anchor() { return 'c'.$this->ID; } /** * Template function: display anchor for permalinks to refer to */ function anchor() { echo ''; } /** * Get the comment author's name. * * @return string */ function get_author_name() { if( $this->get_author_User() ) { return $this->author_User->get_preferred_name(); } else { return $this->author; } } /** * Get the comment author's gender. * * @return string */ function get_author_gender() { if( $this->get_author_User() ) { return $this->author_User->get( 'gender' ); } else { return ''; } } /** * Get the comment anonymous author's name with gender class. * * @param string format to display author * @param array Params * @return string */ function get_author_name_anonymous( $format = 'htmlbody', $params = array() ) { // Make sure we are not missing any param: $params = array_merge( array( 'before' => '', 'after' => '', 'rel' => NULL, ), $params ); $gender_class = ''; if( check_setting( 'gender_colored' ) ) { // Set a gender class if the setting is ON $gender_class = ' nogender'; } $author_name = $this->dget( 'author', $format ); if( is_null( $params['rel'] ) ) { // Set default rel: $params['rel'] = 'bubbletip_comment_'.$this->ID; } if( ! empty( $params['rel'] ) ) { // Initialize attribure "rel" $params['rel'] = ' rel="'.$params['rel'].'"'; } $author_name = '' .$params['before'] .$author_name .$params['after'] .''; return $author_name; } /** * Get the EMail of the comment's author. * * @return string */ function get_author_email() { if( $this->get_author_User() ) { // Author is a user return $this->author_User->get('email'); } else { return $this->author_email; } } /** * Get the URL of the comment's author. * * @return string */ function get_author_url() { if( $this->get_author_User() ) { // Author is a user return $this->author_User->get('url'); } else { return $this->author_url; } } /** * Template function: display the avatar of the comment's author. * * @param string * @param string class for the img tag * @param array */ function avatar( $size = 'crop-top-64x64', $class = 'bCommentAvatar', $params = array() ) { if( $r = $this->get_avatar( $size, $class, $params ) ) { echo $r; } } /** * Get the avatar of the comment's author. * * @param string Avatar thumb size * @param string Class name of avatar img tag * @param array Params * @return string */ function get_avatar( $size = 'crop-top-64x64', $class = 'bCommentAvatar', $params = array() ) { // Make sure we are not missing any param: $params = array_merge( array( 'thumb_zoomable' => false, ), $params ); global $Settings, $Plugins; if( ! $Settings->get('allow_avatars') ) { // Avatars are not allowed generally, Exit here return; } $comment_Item = $this->get_Item(); $comment_Item->load_Blog(); if( !$this->Item->Blog->get_setting('comments_avatars') ) { // Avatars are not allowe for this Blog, Exit here return; } $author_link = get_user_identity_url( $this->author_user_ID ); if( $comment_author_User = & $this->get_author_User() ) { // Author is a user if( $comment_author_User->has_avatar() ) { // Get an image $r = $comment_author_User->get_avatar_imgtag( $size, $class, '', $params['thumb_zoomable'] ); if( $author_link != '' && !$params['thumb_zoomable'] ) { // Add author link $r = ''.$r.''; } return $r; } } // TODO> add new event // See if plugin supplies an image // $img_url = $Plugins->trigger_event( 'GetCommentAvatar', array( 'Comment' => & $this, 'size' => $size ) ); // Get gravatar for anonymous users and for users without uploaded avatar return get_avatar_imgtag_default( $size, $class, '', array( 'email' => $this->get_author_email(), 'username' => $this->get_author_name(), 'gender' => $this->get_author_gender(), ) ); } /** * Template function: display author of comment * * @deprecated use Comment::author2() instead * @param string String to display before author name if not a user * @param string String to display after author name if not a user * @param string String to display before author name if he's a user * @param string String to display after author name if he's a user * @param string Output format, see {@link format_to_output()} * @param boolean true for link, false if you want NO html link * @param string What show as user name: avatar_name | avatar_login | only_avatar | name | login | nickname | firstname | lastname | fullname | preferredname */ function author( $before = '', $after = '#', $before_user = '', $after_user = '#', $format = 'htmlbody', $makelink = false, $lint_text = 'name' ) { echo $this->get_author( array( 'before' => $before, 'after' => $after, 'before_user' => $before_user, 'after_user' => $after_user, 'format' => $format, 'link_to' => ( $makelink ? 'userurl>userpage' : '' ), 'link_text' => $lint_text, ) ); } /** * Template function: display author of comment * * @param array */ function author2( $params = array() ) { echo $this->get_author( $params ); } /** * Get author of comment * * @param array * @return string */ function get_author( $params = array() ) { // Make sure we are not missing any param: $params = array_merge( array( 'profile_tab' => 'user', 'before' => ' ', 'after' => '#', // After anonymous user 'before_user' => '', 'after_user' => '#', // After Member user 'format' => 'htmlbody', 'link_to' => 'userurl>userpage', // 'userpage' or 'userurl' or 'userurl>userpage' 'userpage>userurl' 'link_text' => 'auto', // avatar_name | avatar_login | only_avatar | name | login | nickname | firstname | lastname | fullname | preferredname 'link_rel' => '', 'link_class' => '', 'thumb_size' => 'crop-top-32x32', 'thumb_class' => '', ), $params ); global $Plugins; global $Collection, $Blog; if( empty( $Blog ) ) { // Set Blog if it is still not defined $comment_Item = $this->get_Item(); $Collection = $Blog = $comment_Item->get_Blog(); } if( $Blog->get_setting( 'allow_comments' ) != 'any' && $params['after_user'] == '#' && $params['after'] == '#' ) { // The blog does not allow anonymous comments, Don't display a type of comment author $params['after_user'] = ''; $params['after'] = ''; } if( $params['after_user'] == '#' && $this->is_meta() ) { // Don't display a commenter type for meta comment, because only memebers can create them: $params['after_user'] = ''; } if( !$Blog->get_setting('comments_avatars') && $params['link_text'] == 'avatar' ) { // If avatars are not allowed for this Blog $params['link_text'] = 'name'; } if( $this->get_author_User() ) { // Author is a registered user: if( $params['after_user'] == '#' ) $params['after_user'] = ' ['.T_('Member').']'; $r = $this->author_User->get_identity_link( $params ); $r = $params['before_user'].$r.$params['after_user']; } else { // Not a registered user, display info recorded at edit time: if( $params['after'] == '#' ) $params['after'] = ' ['.T_('Visitor').']'; if( utf8_strlen( $this->author_url ) <= 10 ) { // URL is too short anyways... $params['link_to'] = ''; } $author_name_params = array(); if( strpos( $params['link_text'], 'avatar' ) !== false ) { // Get avatar for anonymous user $author_name_params['before'] = $this->get_avatar( $params['thumb_size'], $params['thumb_class'] ); } // Don't display avatar login name on mode 'only_avatar' $author_name = $params['link_text'] == 'only_avatar' ? $author_name_params['before'] : $this->get_author_name_anonymous( $params['format'], $author_name_params ); switch( $params['link_to'] ) { case 'userurl': case 'userurl>userpage': case 'userpage>userurl': // Make a link: $r = $this->get_author_url_link( $author_name, $params['before'], $params['after'], true, $params['link_class'] ); break; default: // Display the name: (NOTE: get_author_url_link( with nolink option ) would NOT handle this correctly when url is empty $r = $params['before'].$author_name.$params['after']; break; } } $hook_params = array( 'data' => & $r, 'Comment' => & $this, 'makelink' => ! empty($params['link_to']), ); $Plugins->trigger_event( 'FilterCommentAuthor', $hook_params ); return $r; } /** * Template function: display comment's author's IP * * @param string String to display before IP, if IP exists * @param string String to display after IP, if IP exists * @param boolean|string Type of url * TRUE|'filter' - create an url to filter by IP * 'antispam' - to antispam page with filtered by the IP * FALSE - display IP as plain text without link * @param boolean TRUE to display a link to antispam ip page */ function author_ip( $before = '', $after = '', $IP_link_to = false, $display_antispam_link = false ) { if( ! empty( $this->author_IP ) ) { global $Plugins, $CommentList; $author_IP = $this->author_IP; if( $IP_link_to === 'antispam' ) { // Add link to antispam page $author_IP = implode( ', ', get_linked_ip_list( array( $author_IP ) ) ); } elseif( $IP_link_to === 'filter' || $IP_link_to ) { // Add link to filter by IP $filter_IP_url = regenerate_url( 'filter,ctrl,comments,cmnt_fullview_comments', 'cmnt_fullview_author_IP='.$author_IP.'&ctrl=comments' ); $author_IP = ''.$author_IP.''; } echo $before; // Filter the IP by plugins for display, allowing e.g. the DNSBL plugin to add a link that displays info about the IP: echo $Plugins->get_trigger_event( 'FilterIpAddress', array( 'format'=>'htmlbody', 'data' => $author_IP ), 'data' ); if( $display_antispam_link ) { // Display a link to antispam ip page $antispam_icon = get_icon( 'lightning', 'imgtag', array( 'title' => T_( 'Go to edit this IP address in antispam control panel' ) ) ); echo ' '.implode( ', ', get_linked_ip_list( array( $this->author_IP ), NULL, $antispam_icon ) ); } echo $after; } } /** * Template function: display link to comment author's provided email * * @param string String to display for link: leave empty to display email * @param string String to display before email, if email exists * @param string String to display after email, if email exists * @param boolean false if you want NO html link */ function author_email( $linktext='', $before='', $after='', $makelink = true ) { $email = $this->get_author_email(); if( strlen( $email ) > 5 ) { // If email exists: echo $before; if( $makelink ) echo ''; echo ($linktext != '') ? $linktext : $email; if( $makelink ) echo ''; echo $after; } } /** * Template function: display comment's country that was detected by IP address * * @param string String to display before country, if country_ID is defined * @param string String to display after country, if country_ID is defined */ function ip_country( $before = '', $after = '' ) { echo $this->get_ip_country( $before, $after ); } /** * Get comment's country that was detected by IP address * * @param string String to display before country, if country_ID is defined * @param string String to display after country, if country_ID is defined * @return string Country with flag */ function get_ip_country( $before = '', $after = '' ) { $country = ''; if( !empty( $this->IP_ctry_ID ) ) { // Country ID is defined load_funcs( 'regional/model/_regional.funcs.php' ); load_class( 'regional/model/_country.class.php', 'Country' ); $CountryCache = & get_CountryCache(); if( $Country = $CountryCache->get_by_ID( $this->IP_ctry_ID, false ) ) { $country .= $before; $country .= country_flag( $Country->get( 'code' ), $Country->get_name(), 'w16px', 'flag', '', false ); $country .= ' '.$Country->get_name(); $country .= $after; } } return $country; } /** * Get link to comment author's provided URL * * @param string String to display for link: leave empty to display URL * @param string String to display before link, if link exists * @param string String to display after link, if link exists * @param boolean false if you want NO html link * @param string Link class * @return boolean true if URL has been displayed */ function get_author_url_link( $linktext = '', $before = '', $after = '', $makelink = true, $link_class = '' ) { global $Plugins; $url = $this->get_author_url(); if( utf8_strlen( $url ) < 10 ) { return false; } // If URL exists: $r = $before; if( $makelink ) { $r .= 'nofollow ) { $r .= 'rel="nofollow" '; } if( ! empty( $link_class ) ) { $r .= 'class="'.$link_class.'" '; } $r .= 'href="'.$url.'">'; } $r .= ( empty($linktext) ? $url : $linktext ); if( $makelink ) $r .= ''; $r .= $after; $Plugins->trigger_event( 'FilterCommentAuthorUrl', array( 'data' => & $r, 'makelink' => $makelink, 'Comment' => $this ) ); return $r; } /** * Template function: display link to comment author's provided URL * * @param string String to display for link: leave empty to display URL * @param string String to display before link, if link exists * @param string String to display after link, if link exists * @param boolean false if you want NO html link * @return boolean true if URL has been displayed */ function author_url( $linktext='', $before='', $after='', $makelink = true ) { $r = $this->get_author_url_link( $linktext, $before, $after, $makelink ); if( !empty( $r ) ) { echo $r; return true; } return false; } /** * Display author url, delete icon and ban icon if user has proper rights * * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. * @param boolean true to use ajax button * @param boolean true to check user permission to edit this comment and antispam screen * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url */ function author_url_with_actions( $redirect_to = NULL, $ajax_button = false, $check_perms = true, $save_context = true ) { global $current_User; if( $this->author_url( '', ' ', '' ) ) { // There is an URL if( ! $this->get_author_User() && $current_User->check_perm( 'comment!CURSTATUS', 'edit', false, $this ) ) { // Author is anonymous user and we have permission to edit this comment... if( $redirect_to == NULL ) { $redirect_to = regenerate_url( '', 'filter=restore', '', '&' ); } $this->deleteurl_link( $redirect_to, $ajax_button, false, '&', $save_context ); $this->banurl_link( $redirect_to, $ajax_button, true, '&', $save_context ); } echo ''; } } /** * Template function: display spam karma of the comment (in percent) * * "%s" gets replaced by the karma value * * @param string Template string to display, if we have a karma value * @param string Template string to display, if we have no karma value (pre-Phoenix) */ function spam_karma( $template = '%s%', $template_unknown = NULL ) { if( isset($this->spam_karma) ) { echo str_replace( '%s', $this->spam_karma, $template ); } else { if( ! isset($template_unknown) ) { echo /* TRANS: "not available" */ T_('N/A'); } else { echo $template_unknown; } } } /** * Provide link to edit a comment if user has edit rights * * @param string to display before link * @param string to display after link * @param string link text * @param string link title * @param string class name * @param string Glue string for url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. * @return boolean */ function edit_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&', $save_context = true, $redirect_to = NULL ) { global $current_User, $admin_url; if( ! is_logged_in( false ) ) return false; if( empty($this->ID) ) { // Happens in Preview return false; } if( ! $current_User->check_perm( 'comment!CURSTATUS', 'edit', false, $this ) ) { // If User has no permission to edit this comment: return false; } if( $text == '#' ) $text = get_icon( 'edit' ).' '.T_('Edit...'); if( $title == '#' ) $title = T_('Edit this comment'); $this->get_Item(); $item_Blog = & $this->Item->get_Blog(); echo $before; if( $item_Blog->get_setting( 'in_skin_editing' ) && !is_admin_page() ) { echo 'ID.$glue.'action=edit'.$glue.'comment_ID='.$this->ID; } if( $save_context ) { // Use a param to redirect after action: if( $redirect_to === NULL ) { // Get current url for redirect: $redirect_to = regenerate_url( '', 'filter=restore', '', '&' ); } echo $glue.'redirect_to='.rawurlencode( $redirect_to ); } echo '" title="'.$title.'"'; echo empty( $class ) ? '' : ' class="'.$class.'"'; if( $this->is_meta() ) { // Edit meta comment by ajax echo ' onclick="return edit_comment( \'form\', '.$this->ID.' )"'; } echo '>'.$text.''; echo $after; return true; } /** * Display delete icon for deleting author_url if user has proper rights * * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. * @param boolean true if create ajax button * @param boolean true if need permission check, because it wasn't checked before * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @return link on success, false otherwise */ function deleteurl_link( $redirect_to, $ajax_button = false, $check_perm = true, $glue = '&', $save_context = true ) { global $current_User, $admin_url; if( ! is_logged_in( false ) ) return false; if( $check_perm && ! $current_User->check_perm( 'comment!CURSTATUS', 'delete', false, $this ) ) { // If current user has no permission to edit this comment return false; } if( $save_context ) { // Use a param to redirect after action: if( $redirect_to === NULL ) { // Get current url for redirect: $redirect_to = regenerate_url( '', 'filter=restore', '', '&' ); } $redirect_to = $glue.'redirect_to='.rawurlencode( $redirect_to ); } else { // Don't allow a redirect after action: $redirect_to = ''; } $delete_url = $admin_url.'?ctrl=comments'.$glue.'action=delete_url'.$glue.'comment_ID='.$this->ID.$glue.url_crumb( 'comment' ).$redirect_to; if( $ajax_button ) { echo ' '.get_icon( 'remove' ).''; } else { echo ' '.get_icon( 'remove' ).''; } } /** * Display ban icon, which goes to the antispam screen with keyword=author_url * * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. * @param boolean true if create ajax button * @param boolean true if need permission check, because it wasn't check before * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @return link on success, false otherwise */ function banurl_link( $redirect_to, $ajax_button = false, $check_perm = true, $glue = '&', $save_context = true ) { global $current_User, $admin_url; if( ! is_logged_in( false ) ) return false; //$Item = & $this->get_Item(); if( $check_perm && ! $current_User->check_perm( 'spamblacklist', 'edit' ) ) { // if current user has no permission to edit spams return false; } if( $save_context ) { // Use a param to redirect after action: if( $redirect_to === NULL ) { // Get current url for redirect: $redirect_to = regenerate_url( '', 'filter=restore', '', '&' ); } $redirect_to = $glue.'redirect_to='.rawurlencode( $redirect_to ); } else { // Don't allow a redirect after action: $redirect_to = ''; } // TODO: really ban the base domain! - not by keyword $ban_domain = get_ban_domain( $this->get_author_url() ); $ban_url = $admin_url.'?ctrl=antispam'.$glue.'action=ban'.$glue.'keyword='.rawurlencode( $ban_domain ).$redirect_to.$glue.url_crumb( 'antispam' ); if( $ajax_button ) { echo ' '.get_icon( 'lightning' ).''; } else { echo ' '.get_icon( 'lightning' ).' '; } } /** * Displays button for deleting the Comment if user has proper rights * * @param string to display before link * @param string to display after link * @param string link text * @param string link title * @param string class name * @param boolean true to make this a button instead of a link * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @param boolean true if create AJAX button * @param string confirmation text * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. */ function delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $glue = '&', $save_context = true, $ajax_button = false, $confirm_text = '#', $redirect_to = NULL ) { global $current_User, $admin_url; if( ! is_logged_in( false ) ) return false; if( empty($this->ID) ) { // Happens in Preview return false; } $this->get_Item(); if( ! $current_User->check_perm( 'comment!CURSTATUS', 'delete', false, $this ) ) { // If User has no permission to delete a comments: return false; } if( $text == '#' ) { // Use icon+text as default, if not displayed as button (otherwise just the text) $text = ( $this->status == 'trash' || $this->is_meta() ) ? T_('Delete').'!' : T_('Recycle').'!'; if( ! $button ) { // Append icon before text $text = ( $this->status == 'trash' || $this->is_meta() ? get_icon( 'delete' ) : get_icon( 'recycle' ) ).' '.$text; } } if( $title == '#' ) { // Set default title $title = ( $this->status == 'trash' || $this->is_meta() ) ? T_('Delete this comment') : T_('Recycle this comment'); } $url = $admin_url.'?ctrl=comments'.$glue.'action=delete'.$glue.'comment_ID='.$this->ID.$glue.url_crumb('comment'); if( $save_context ) { // Use a param to redirect after action: if( $redirect_to === NULL ) { // Get current url for redirect: $redirect_to = regenerate_url( '', 'filter=restore', '', '&' ); } $url .= $glue.'redirect_to='.rawurlencode( $redirect_to ); } echo $before; if( $ajax_button && ( $this->status != 'trash' ) ) { $comment_type = $this->is_meta() ? 'meta' : 'feedback'; echo ''.$text.''; } else { // JS confirm is required only when the comment is not in the recycle bin yet $display_js_confirm = ( $this->status == 'trash' ); if( $display_js_confirm && ( $confirm_text == '#' ) ) { // Set js confirm text on comment delete action $confirm_text = TS_('You are about to delete this comment!\\nThis cannot be undone!'); } if( $button ) { // Display as button echo ''; } else { // Display as link echo ''.$text.''; } } echo $after; return true; } /** * Provide link to deprecate a comment if user has edit rights * * @param string to display before link * @param string to display after link * @param string link text * @param string link title * @param string class name * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @param boolean true if create AJAX button * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. * @return string A link to deprecate this comment */ function get_deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&', $save_context = true, $ajax_button = false, $redirect_to = NULL ) { global $current_User, $admin_url; if( ! is_logged_in( false ) ) { return false; } if( ( $this->status == 'deprecated' ) // Already deprecated! || !$current_User->check_perm( 'comment!deprecated', 'moderate', false, $this ) ) { // User has no right to deprecated this comment: return false; } $status = 'deprecated'; $status_order = get_visibility_statuses( 'ordered-array' ); $status_index = get_visibility_statuses( 'ordered-index', array( 'redirected' ) ); if( isset( $status_index[ $status ] ) && isset( $status_order[ $status_index[ $status ] ] ) && ! empty( $status_order[ $status_index[ $status ] ][3] ) ) { // Get color of button icon $status_icon_color = $status_order[ $status_index[ $status ] ][3]; } else { // Use grey arrow as default $status_icon_color = 'grey'; } $params = array( 'before' => $before, 'after' => $after, 'text' => ( ( $text == '#' ) ? get_icon( 'move_down_'.$status_icon_color ).' '.T_('Deprecate').'!' : $text ), 'title' => $title, 'class' => $class, 'glue' => $glue, 'save_context' => $save_context, 'ajax_button' => $ajax_button, 'redirect_to' => $redirect_to, 'status' => 'deprecated', 'action' => 'restrict' ); return $this->get_moderation_link( $params ); } /** * Provide link to vote a comment if user has edit rights * * @param string a vote type * @param string a vote value * @param string class name * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @param boolean true if create AJAX button * @param array Params */ function get_vote_link( $vote_type, $vote_value, $class = '', $glue = '&', $save_context = true, $ajax_button = false, $params = array() ) { $params = array_merge( array( 'title_spam' => T_('Cast a spam vote!'), 'title_spam_voted' => T_('You sent a spam vote.'), 'title_notsure' => T_('Cast a "not sure" vote!'), 'title_notsure_voted' => T_('You sent a "not sure" vote.'), 'title_ok' => T_('Cast an OK vote!'), 'title_ok_voted' => T_('You sent an OK vote.'), 'title_yes' => T_('Cast a helpful vote!'), 'title_yes_voted' => T_('You sent a "helpful" vote.'), 'title_no' => T_('Cast a "not helpful" vote!'), 'title_no_voted' => T_('You sent a "not helpful" vote.'), ), $params ); global $current_User, $admin_url; $this->get_Item(); $is_voted = false; $icon_params = array(); if( $class == 'voted' ) { // Current user already voted for this $class = ''; $is_voted = true; $icon_params = array( 'class' => 'voted' ); } switch( $vote_value ) { case "spam": $title = $is_voted ? $params['title_spam_voted'] : $params['title_spam']; $icon_params['title'] = $title; $text = get_icon( 'vote_spam'.( $class != '' ? '_'.$class : '' ), 'imgtag', $icon_params ); $class .= ' '.button_class(); break; case "notsure": $title = $is_voted ? $params['title_notsure_voted'] : $params['title_notsure']; $icon_params['title'] = $title; $text = get_icon( 'vote_notsure'.( $class != '' ? '_'.$class : '' ), 'imgtag', $icon_params ); $class .= ' '.button_class(); break; case "ok": $title = $is_voted ? $params['title_ok_voted'] : $params['title_ok']; $icon_params['title'] = $title; $text = get_icon( 'vote_ok'.( $class != '' ? '_'.$class : '' ), 'imgtag', $icon_params ); $class .= ' '.button_class(); break; case "yes": $title = $is_voted ? $params['title_yes_voted'] : $params['title_yes']; $icon_params['title'] = $title; $text = get_icon( 'thumb_up'.( $class != '' ? '_'.$class : '' ), 'imgtag', $icon_params ); break; case "no": $title = $is_voted ? $params['title_no_voted'] : $params['title_no']; $icon_params['title'] = $title; $text = get_icon( 'thumb_down'.( $class != '' ? '_'.$class : '' ), 'imgtag', $icon_params ); break; } if( strpos( $class, 'disabled' ) !== false ) { // add rollover action for disabled buttons $class .= ' rollover_sprite'; } $class .= ' action_icon'; // change classes for bootstrap styles if( $is_voted ) { $class .= ' active'; } $class = str_replace( 'disabled', '', $class ); $r = 'ID.', \''.$vote_type.'\' , \''.$vote_value.'\' ); return false;"'; } $r .= ' title="'.$title.'" class="'.$class.'">'.$text.''; return $r; } /** * Display link to deprecate a comment if user has edit rights * * @param string to display before link * @param string to display after link * @param string link text * @param string link title * @param string class name * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @param boolean true if create AJAX button * @param string Redirect url. NOTE: This param MUST NOT be encoded before sending to this func, because it is executed by this func inside. */ function deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&', $save_context = true, $ajax_button = false, $redirect_to = NULL ) { $deprecate_link = $this->get_deprecate_link( $before, $after, $text, $title, $class, $glue, $save_context, $ajax_button, $redirect_to ); if( $deprecate_link === false ) { // The deprecate link is unavailable for current user and for this comment return false; } // Display the deprecate link echo $deprecate_link; return true; } /** * Display link to vote a comment as SPAM if user has edit rights * * @param string to display before link * @param string to display after link * @param string glue between url params * @param boolean TRUE - to save context(memorized params), to allow append redirect_to param to url * @param boolean true if create AJAX button * @param array Params */ function vote_spam( $before = '', $after = '', $glue = '&', $save_context = true, $ajax_button = false, $params = array()) { $params = array_merge( array( 'display' => false, // TRUE - to show this tool on loading(Used to make it visible only when JS is enalbed) 'title_spam' => T_('Cast a spam vote!'), 'title_spam_voted' => T_('You sent a spam vote.'), 'title_notsure' => T_('Cast a "not sure" vote!'), 'title_notsure_voted' => T_('You sent a "not sure" vote.'), 'title_ok' => T_('Cast an OK vote!'), 'title_ok_voted' => T_('You sent an OK vote.'), 'title_empty' => T_('No votes on spaminess yet.'), 'button_group_class' => button_class( 'group' ), ), $params ); if( $this->is_meta() ) { // Don't allow voting on meta comments: return; } global $current_User; $this->get_Item(); if( !is_logged_in( false ) || !$current_User->check_perm( 'blog_vote_spam_comments', 'edit', false, $this->Item->get_blog_ID() ) ) { // If User has no permission to vote spam return false; } echo $before; $style = $params['display'] ? '' : ' style="display:none"'; echo '
', 'after' => '
' ) ); } echo 'Subject: $subject\n$notify_message"; if( $debug >= 2 ) { // output mail content - NOTE: this will kill sending of headers. echo "
$mail_dump
"; } $Debuglog->add( $mail_dump, 'notification' ); } // Send the email: // Note: Note activated users won't get notification email send_mail_to_User( $notify_user_ID, $subject, 'comment_new', $email_template_params, false, array( 'Reply-To' => $user_reply_to ) ); blocked_emails_memorize( $notify_User->email ); locale_restore_previous(); } blocked_emails_display(); } /** * Send "comment spam" emails for those users who have permission to moderate this comment. */ function send_vote_spam_emails() { global $current_User, $Settings, $UserSettings; if( ! is_logged_in() ) { // Only loggen in users can vote on comments return; } if( $this->is_meta() ) { // Meta comments have no spam voting return; } $UserCache = & get_UserCache(); $comment_Item = & $this->get_Item(); $comment_item_Blog = & $comment_Item->get_Blog(); $coll_owner_User = $comment_item_Blog->get_owner_User(); $moderators = array(); $moderators_to_notify = $comment_item_Blog->get_comment_moderator_user_data(); foreach( $moderators_to_notify as $moderator ) { $notify_moderator = is_null( $moderator->notify_spam_cmt_moderation ) ? $Settings->get( 'def_notify_spam_cmt_moderation' ) : $moderator->notify_spam_cmt_moderation; if( $notify_moderator ) { // Include user to notify because of enabled setting: $moderators[] = $moderator->user_ID; } } if( $UserSettings->get( 'notify_spam_cmt_moderation', $coll_owner_User->ID ) && is_email( $coll_owner_User->get( 'email' ) ) ) { // Include collection owner: $moderators[] = $coll_owner_User->ID; } $email_subject = sprintf( T_('[%s] Spam comment may need moderation on "%s"'), $comment_item_Blog->get( 'shortname' ), $comment_Item->get( 'title' ) ); $email_template_params = array( 'Comment' => $this, 'Blog' => $comment_item_Blog, 'Item' => $comment_Item, 'voter_ID' => $current_User->ID, ); // Load all moderators, and check each edit permission on this comment: $UserCache->load_list( $moderators ); foreach( $moderators as $moderator_ID ) { if( $moderator_ID == $current_User->ID ) { // Don't send email to the voter: continue; } $moderator_User = $UserCache->get_by_ID( $moderator_ID, false ); if( $moderator_User && $moderator_User->check_perm( 'comment!CURSTATUS', 'edit', false, $this ) ) { // If moderator has a permission to edit this comment: $moderator_Group = $moderator_User->get_Group(); $email_template_params['notify_full'] = $moderator_Group->check_perm( 'comment_moderation_notif', 'full' ); // Send email to the moderator: send_mail_to_User( $moderator_ID, $email_subject, 'comment_spam', $email_template_params ); } } } /** * Handle quick moderation secret param: checks if comment secret should expire after first comment moderation, and delete the secret if required * This should be called after every kind of commment moderation */ function handle_qm_secret( $save_comment = false ) { $comment_Item = & $this->get_Item(); $comment_Item->load_Blog(); if( $comment_Item->Blog->get_setting( 'comment_quick_moderation' ) == 'expire' ) { // comment secret expires after first comment moderation $this->set( 'secret', NULL ); } if( $save_comment ) { $this->dbupdate(); } } /** * Get a list of those comment statuses which can be displayed in the front office * * @return array Front office statuses in the comment's collection */ function get_frontoffice_statuses() { if( ! ( $comment_Item = & $this->get_Item() ) || ! ( $comment_blog_ID = $comment_Item->get_blog_ID() ) ) { // Comment's collection ID must be detected to get front-office comment statuses: return array(); } return get_inskin_statuses( $comment_blog_ID, 'comment' ); } /** * Check if this comment may be seen in front office * * @return boolean true if the comment status is used to display on front office, false otherwise */ function may_be_seen_in_frontoffice() { $current_status_permvalue = get_status_permvalue( $this->status ); $frontoffice_statuses_permvalue = get_status_permvalue( $this->get_frontoffice_statuses() ); return ( $current_status_permvalue & $frontoffice_statuses_permvalue ) ? true : false; } /** * Trigger event AfterCommentUpdate after calling parent method. * * @return boolean true on success */ function dbupdate() { global $Plugins, $DB; if( isset( $this->previous_status ) ) { // Restrict comment status by parent item: // (ONLY if current request is updating comment status) $this->restrict_status( true ); } $dbchanges = $this->dbchanges; if( count( $dbchanges ) ) { $this->set_last_touched_date(); } $DB->begin(); if( ( $r = parent::dbupdate() ) !== false ) { if( isset( $dbchanges['comment_content'] ) || isset( $dbchanges['comment_renderers'] ) ) { // Delete a prerendered content if content or text renderers have been updated: $this->delete_prerendered_content(); } $update_item_contents_last_updated_date = false; if( $this->may_be_seen_in_frontoffice() ) { // Update contents last update date of the comment's post ONLY when the updated comment may be seen in frontoffice: if( isset( $dbchanges['comment_content'] ) || isset( $dbchanges['comment_rating'] ) || isset( $dbchanges['comment_item_ID'] ) || ( isset( $dbchanges['comment_status'] ) && isset( $this->previous_status ) && ! $this->can_be_displayed( $this->previous_status ) ) ) { // AND if content, rating or parent Item have been updated // or status has been updated from NOT front-office status into some front-office status: $update_item_contents_last_updated_date = true; } } if( !empty( $this->previous_item_ID ) ) { // Comment is moved from another post $ItemCache = & get_ItemCache(); $ItemCache->clear(); if( $previous_Item = & $ItemCache->get_by_ID( $this->previous_item_ID, false, false ) ) { // Update ONLY last touched date of previous item: $previous_Item->update_last_touched_date( false, true ); } // Also move all child comments to new post $child_comment_IDs = $this->get_child_comment_IDs(); if( count( $child_comment_IDs ) ) { $DB->query( 'UPDATE T_comments SET comment_item_ID = '.$DB->quote( $this->item_ID ).' WHERE comment_ID IN ( '.$DB->quote( $child_comment_IDs ).' )' ); } } $this->update_last_touched_date( true, $update_item_contents_last_updated_date ); $DB->commit(); $Plugins->trigger_event( 'AfterCommentUpdate', $params = array( 'Comment' => & $this, 'dbchanges' => $dbchanges ) ); } else { $DB->rollback(); } return $r; } /** * Get karma and set it before adding the Comment to DB. * * @return boolean true on success, false if it did not get inserted */ function dbinsert() { /** * @var Plugins */ global $Plugins; global $Settings; if( isset( $this->previous_status ) ) { // Restrict comment status by parent item: // (ONLY if current request is updating comment status) $this->restrict_status( true ); } // Get karma percentage (interval -100 - 100) $spam_karma = $Plugins->trigger_karma_collect( 'GetSpamKarmaForComment', array( 'Comment' => & $this ) ); $this->set_spam_karma( $spam_karma ); // Change status accordingly: if( ! is_null($spam_karma) ) { if( $spam_karma < $Settings->get('antispam_threshold_publish') ) { // Publish: $this->set( 'status', 'published' ); } elseif( $spam_karma > $Settings->get('antispam_threshold_delete') ) { // Delete/No insert: return false; } } // set comment secret for quick moderation // fp> users have requested this for all comments $comment_Item = & $this->get_Item(); $comment_Blog = & $comment_Item->get_Blog(); if( $comment_Blog->get_setting( 'comment_quick_moderation' ) != 'never' ) { // quick moderation is permitted, set comment secret $this->set( 'secret', generate_random_key() ); } $this->set_last_touched_date(); $dbchanges = $this->dbchanges; if( $r = parent::dbinsert() ) { // Update last touched date of item if comment is created with ANY status, // But update contents last updated date of item if comment is created ONLY in published status(Public, Community or Members): $this->update_last_touched_date( true, $this->may_be_seen_in_frontoffice() ); // Plugin event to call after new comment insert: $Plugins->trigger_event( 'AfterCommentInsert', $params = array( 'Comment' => & $this, 'dbchanges' => $dbchanges ) ); } return $r; } /** * Trigger event AfterCommentDelete after calling parent method. * * @param boolean set true to force permanent delete, leave false for "move to trash/recylce" * @param boolean TRUE to use transaction * @return boolean true on success */ function dbdelete( $force_permanent_delete = false, $use_transaction = true ) { global $Plugins, $DB; if( $use_transaction ) { $DB->begin(); } if( $this->status != 'trash' ) { // The comment was not recycled yet if( $this->has_replies() ) { // Move the replies to the one level up $new_parent_ID = !empty( $this->in_reply_to_cmt_ID ) ? $DB->quote( $this->in_reply_to_cmt_ID ) : 'NULL'; $DB->query( 'UPDATE T_comments SET comment_in_reply_to_cmt_ID = '.$new_parent_ID.' WHERE comment_in_reply_to_cmt_ID = '.$this->ID ); } } // Get the latest Comment of parent Item in order to know to refresh $comment_Item = & $this->get_Item(); if( $item_latest_Comment = & $comment_Item->get_latest_Comment() && $item_latest_Comment->ID == $this->ID ) { // We should refresh last touched date after deleting of this Comment because it was the latest comment of parent Item: $refresh_parent_item = true; } if( $force_permanent_delete || ( $this->status == 'trash' ) || $this->is_meta() ) { // Permamently delete comment from DB: // remember ID, because parent method resets it to 0 $old_ID = $this->ID; if( $r = parent::dbdelete() ) { // re-set the ID for the Plugin event $this->ID = $old_ID; $Plugins->trigger_event( 'AfterCommentDelete', $params = array( 'Comment' => & $this ) ); $this->ID = 0; } } else { // don't delete, just move to the trash: $this->set( 'status', 'trash' ); $r = $this->dbupdate(); } if( $r ) { if( $this->ID == 0 ) { // Update only last touched date of item if comment was deleted from DB, // Don't call this when comment was recycled because we already called this on dbupdate() above: $this->update_last_touched_date(); } if( $use_transaction ) { $DB->commit(); } if( ! empty( $refresh_parent_item ) ) { // Refresh contents last updated ts of parent Item if this Comment was the latest Comment of parent Item: $comment_Item->latest_Comment = NULL; $comment_Item->refresh_contents_last_updated_ts(); } } else { if( $use_transaction ) { $DB->rollback(); } } return $r; } /** * Displays link for replying to the Comment if blog's setting allows this action * * @param string to display before link * @param string to display after link * @param string link text * @param string link title * @param string class name */ function reply_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '' ) { if( ! is_logged_in( false ) ) { return false; } if( empty( $this->ID ) ) { // Happens in Preview return false; } $this->get_Item(); $this->Item->load_Blog(); if( ! $this->Item->Blog->get_setting( 'threaded_comments' ) ) { // A blog's setting is OFF for replying to the comment return false; } if( !$this->Item->can_comment() ) { // The comments are disabled return false; } // ID of a replying comment $comment_reply_ID = param( 'reply_ID', 'integer', 0 ); if( $text == '#' ) { // Use default text $text = $this->ID == $comment_reply_ID ? T_('You are currently replying to this comment') : T_('Reply to this comment'); } if( $title == '#' ) { // Use default title $title = T_('Reply to this comment'); } $class .= ' comment_reply'; if( $this->ID == $comment_reply_ID ) { // This comment is using for replying now $class .= ' active'; } $class = ' class="'.trim( $class ).'"'; // Initialize an url to reply on comment: if( is_admin_page() ) { // for back-office: global $admin_url; $url = $admin_url.'?ctrl=items&blog='.$this->Item->Blog->ID.'&p='.$this->Item->ID.( $this->is_meta() ? '&comment_type=meta' : '' ).'&reply_ID='.$this->ID.'#comment_checkchanges'; } else { // for front-office: $url = url_add_param( $this->Item->get_permanent_url(), 'reply_ID='.$this->ID.( $this->is_meta() ? '&comment_type=meta' : '' ).'&redir=no' ).'#'.( $this->is_meta() ? 'meta_' : '' ).'form_p'.$this->Item->ID; } echo $before; // Display a link echo ''.$text.''; echo $after; return true; } /** * Check if comment has the replies */ function has_replies() { global $cache_comments_has_replies; if( ! isset( $cache_comments_has_replies ) ) { // Init an array to cache $cache_comments_has_replies = array(); } if( ! isset( $cache_comments_has_replies[ $this->item_ID ] ) ) { // Get all comments that have the replies from DB (first time) global $DB; // Cache a result $SQL = new SQL(); $SQL->SELECT( 'DISTINCT ( comment_in_reply_to_cmt_ID ), comment_ID' ); $SQL->FROM( 'T_comments' ); $SQL->WHERE( 'comment_in_reply_to_cmt_ID IS NOT NULL' ); $SQL->WHERE_and( 'comment_item_ID = '.$this->item_ID ); // Init an array to cache a result from current item $cache_comments_has_replies[ $this->item_ID ] = $DB->get_assoc( $SQL->get() ); } // Get a result from cache return isset( $cache_comments_has_replies[ $this->item_ID ][ $this->ID ] ); } /** * Set field last_touched_ts */ function set_last_touched_date() { global $localtimenow; $this->set_param( 'last_touched_ts', 'date', date2mysql( $localtimenow ) ); } /** * Update field last_touched_ts * * @param boolean update comment's post last touched ts as well or not * @param boolean Use TRUE to update field contents_last_updated_ts of the comment's item */ function update_last_touched_date( $update_item_last_touched_ts = true, $update_item_contents_last_updated_ts = false ) { global $localtimenow, $current_User; if( $this->is_meta() ) { // Don't touch Item when this Comment is meta return; } $comment_Item = & $this->get_Item(); if( empty( $comment_Item ) ) { // Don't execute the following code because this comment is broken return; } $timestamp = date2mysql( $localtimenow ); if( $this->ID && ( $this->last_touched_ts !== $timestamp ) ) { // If the comment was not deleted then update last touched date $this->set_param( 'last_touched_ts', 'date', $timestamp ); $this->dbupdate(); } if( $update_item_last_touched_ts || $update_item_contents_last_updated_ts ) { // Update last touched timestamp or content last update timestamp of the Item: $comment_Item->update_last_touched_date( true, $update_item_last_touched_ts, $update_item_contents_last_updated_ts ); } } /** * Get a permalink link to the Item of this Comment * * @param array Params * @return string Link to Item with anchor to Comment */ function get_permanent_item_link( $params = array() ) { $params = array_merge( array( 'text' => '#item#', 'title' => '#', 'class' => '', 'nofollow' => false, 'restrict_status' => false, ), $params ); return $this->get_permanent_link( $params['text'], $params['title'], $params['class'], $params['nofollow'], $params['restrict_status'] ); } /** * Check if this comment is meta * * @return boolean TRUE if this comment is meta */ function is_meta() { return $this->type == 'meta'; } /** * Get all child comment IDs * * @param integer Parent comment ID * @return array Comment IDs */ function get_child_comment_IDs( $parent_comment_ID = NULL ) { global $DB; if( $parent_comment_ID === NULL ) { // Use current comment ID as main parent ID $parent_comment_ID = $this->ID; } // Get child comment of level 1 $comments_SQL = new SQL(); $comments_SQL->SELECT( 'comment_ID' ); $comments_SQL->FROM( 'T_comments' ); $comments_SQL->WHERE( 'comment_in_reply_to_cmt_ID = '.$parent_comment_ID ); $parent_comment_IDs = $DB->get_col( $comments_SQL->get() ); $comment_IDs = array(); foreach( $parent_comment_IDs as $comment_ID ) { // Get all children recursively $comment_IDs[] = $comment_ID; $child_comment_IDs = $this->get_child_comment_IDs( $comment_ID ); foreach( $child_comment_IDs as $child_comment_ID ) { $comment_IDs[] = $child_comment_ID; } } return $comment_IDs; } /* * Get max allowed comment status depending on parent item status * * @param string Status key to check if it is allowed, NULL- to use current comment status * @return string Status key */ function get_allowed_status( $current_status = NULL ) { $comment_Item = & $this->get_Item(); $item_Blog = & $comment_Item->get_Blog(); if( $current_status === NULL ) { // Use current comment status: $current_status = $this->get( 'status' ); } // Restrict status to max allowed for item collection: $item_restricted_status = $item_Blog->get_allowed_item_status( $comment_Item->get( 'status' ) ); if( empty( $item_restricted_status ) ) { // If max allowed status is not detected because for example current User has no perm to item status, // then use current status of the Item in order to restrict max comment status below: $item_restricted_status = $comment_Item->get( 'status' ); } // Comment status cannot be more than post status, restrict it: $restricted_statuses = get_restricted_statuses( $item_Blog->ID, 'blog_comment!', 'edit', '', $item_restricted_status, $this ); // Get all visibility statuses: $visibility_statuses = get_visibility_statuses( '', $restricted_statuses ); // Find what max comment status we can use depending on parent item: $status_order = 0; $comment_status_order = NULL; $item_status_order = NULL; $max_allowed_comment_status = ''; foreach( $visibility_statuses as $visibility_status => $visibility_status_title ) { if( $status_order == 0 ) { // Set max allowed comment status: $max_allowed_comment_status = $visibility_status; } if( $visibility_status == $current_status ) { // Set an order for current status of this comment: $comment_status_order = $status_order; } if( $visibility_status == $item_restricted_status ) { // Set an order for max allowed status of the comment's item: $item_status_order = $status_order; } $status_order++; } if( $comment_status_order === NULL ) { // Current comment status is higher than max allowed by parent item, // So restrict it by max allowed for comments: $comment_restricted_status = $max_allowed_comment_status; } elseif( $comment_status_order < $item_restricted_status ) { // Restrict comment status to max allowed by parent item: $comment_restricted_status = $item_restricted_status; } else { // Don't restrict because current comment status is allowed: $comment_restricted_status = $current_status; } return $comment_restricted_status; } /** * Restrict Comment status by parent Item status AND its Collection access restriction AND by CURRENT USER write perm * * @param boolean TRUE to update status */ function restrict_status( $update_status = false ) { global $current_User; // Store current status to display a warning: $current_status = $this->get( 'status' ); $commented_Item = & $this->get_Item(); if( $this->is_meta() ) { // Meta comment: if( ! is_logged_in() || ( $commented_Item && ! $current_User->check_perm( 'meta_comment', 'view', false, $commented_Item->get_blog_ID() ) ) ) { // Change meta comment status to 'protected' if user has no perm to view them: $comment_allowed_status = 'protected'; } else { // Do not restrict if meta comment and user has the proper permission: $comment_allowed_status = $current_status; } } else { // Restrict status of normal comment to max allowed by parent item: $comment_allowed_status = $this->get_allowed_status(); if( empty( $comment_allowed_status ) && $commented_Item && ( $item_Blog = & $commented_Item->get_Blog() ) ) { // If min allowed status is not found then use what default status is allowed: $comment_allowed_status = get_highest_publish_status( 'comment', $item_Blog->ID, false ); } } if( $update_status ) { // Update status to new restricted value: $this->set( 'status', $comment_allowed_status ); } else { // Only change status to update it on the edit forms and Display a warning: $this->status = $comment_allowed_status; if( $current_status != $this->get( 'status' ) && ! $this->is_meta() ) { // If current comment status cannot be used because it is restricted by parent item: global $Messages; // Get max allowed for item collection: $comment_Item = & $this->get_Item(); $item_Blog = & $comment_Item->get_Blog(); $item_restricted_status = $item_Blog->get_allowed_item_status( $comment_Item->status ); // Get all visibility status titles: $visibility_statuses = get_visibility_statuses(); // Display a warning: $Messages->add( sprintf( T_('Since the parent post of this comment have its visibility set to "%s", the visibility of this comment will be restricted to "%s".'), $visibility_statuses[ $item_restricted_status ], $visibility_statuses[ $this->status ] ), 'warning' ); } } } /** * Check what were already notified on this item * * @param array|string Flags, possible values: 'moderators_notified', 'members_notified', 'community_notified' */ function check_notifications_flags( $flags ) { if( ! is_array( $flags ) ) { // Convert string to array: $flags = array( $flags ); } // TRUE if all requested flags are in current item notifications flags: return ( count( array_diff( $flags, $this->get( 'notif_flags' ) ) ) == 0 ); } /** * Check if this comment can be displayed for current user on front-office * * @param string|NULL Status | NULL to use current status of this comment * @return boolean */ function can_be_displayed( $status = NULL ) { if( empty( $this->ID ) ) { // Comment is not created yet, so it cannot be displayed: return false; } // Load Item of this comment to get a collection ID: $Item = & $this->get_Item(); if( $status === NULL ) { // Use current status of this comment: $status = $this->get( 'status' ); } return can_be_displayed_with_status( $status, 'comment', $Item->get_blog_ID(), $this->author_user_ID ); } /** * Get comment order numbers for current filtered list (global $CommentList) * * @return integer|NULL */ function get_inlist_order() { if( empty( $this->ID ) ) { // This comment must exist in DB return NULL; } global $CommentList; if( empty( $CommentList ) ) { // Comment list must be initialized globally return NULL; } if( ! isset( $CommentList->inlist_orders[ $this->ID ] ) ) { // Order number is not found in list for this comment: return NULL; } $inlist_order = intval( $CommentList->inlist_orders[ $this->ID ] ); return $inlist_order < 0 ? 0 : $inlist_order; } } ?>