<?php
// $commentdata['comment_date_gmt'] = current_time('mysql', 1);

/*
 * Implementation of the Activty Stream
 */
class PeepSoActivity extends PeepSoAjaxCallback
{
    private $post_list = NULL;
    private $post_idx = 0;

    public $query_type = NULL;				// type of query, the CPT name. used in filter_post_clauses() to adjust WHERE
    public $post_query = NULL;				// WP_Query instance for post queries
    public $post_data = NULL;				// $posts value returned from latest show_post() call

    public $pinned = FALSE;					// TRUE = get only pinned posts
    public $stream_id = 1;					// MODULE_ID of the stream tab
    public $is_loading_stream = FALSE;      // TRUE - we are actually loading ites on the stream (not editing etc)
    public $context = NULL;					// type of context view

    public $comment_query = NULL;			// WP_Query instance for comment queries
    public $count_comment_query = NULL;			// WP_Query instance for comment count queries
    public $comment_data = NULL;			// $posts value returned from latest show_comment() call
    public $is_comment_query = FALSE;      // TRUE, if it was from comments query

    const TABLE_NAME = 'peepso_activities';
    const BLOCK_TABLE_NAME = 'peepso_blocks';
    const SAVED_POSTS_TABLE_NAME = 'peepso_saved_posts';
    const ACTIVITY_FOLLOWERS_TABLE_NAME = 'peepso_activity_followers';

    const MODULE_ID = 1;

    private $owner_id = 0;					// used in modifying query
    private $user_id = NULL;				// used to override the user_id in unit tests
//	private $current_user = 0;				// used in modifying query
    private $post_media = NULL;				// contains the array for the comment/post box media
    private $peepso_media = array();		// contains the array for the $post_media
    private $last_post_id = NULL; 			// used in filter_since_id() to get posts after this ID
    private $first_post_id = NULL; 			// used in filter_before_id() to get posts before this ID
    private $oembed_title = NULL;
    private $oembed_description = NULL;

    const ACTIVITY_LIMIT_PAGE_LOAD = 1;
    const ACTIVITY_LIMIT_BELOW_FOLD = 3;

    /**
     * Called from PeepSoAjaxHandler
     * Declare methods that don't need auth to run
     * @return array
     */
    public function ajax_auth_exceptions()
    {
        $list_exception = array();
        $hide_from_guest = PeepSo::get_option('site_activity_hide_stream_from_guest', 0);
        if(!$hide_from_guest ) {
            array_push($list_exception, 'show_posts_per_page');
        }

        return $list_exception;
    }

    public function __construct()
    {
        parent::__construct();

        add_action('peepso_activity_after_add_post', array(&$this, 'handle_embed_data'), 10, 2);
        add_filter('peepso_activity_post_content', array(&$this, 'activity_post_content'), 10, 1);
        add_filter('peepso_privacy_access_levels', array(&$this, 'privacy_access_levels'), 10, 1);
        add_filter('oembed_dataparse', array(&$this, 'set_media_properties'), 10, 2);
        // Run this last to give priority to addons
        add_filter('peepso_activity_get_post', array(&$this, 'activity_get_post'), 90, 4);

        // add Vine to the list of allowed oEmbed providers
        // fallback for functions that go straight to oEmbed
        wp_oembed_add_provider(
            '#https?://vine\.co/v/([a-z0-9]+)\/?#i', // URL format to match
            'https://vine.co/oembed.{format}', // oEmbed provider URL
            TRUE                               // regex URL format
        );

        /**
         * @jaworskimatt at 22-07-2016 added singleton = TRUE to the constructor
         * @jaworskimatt at 13-10-2016 trying to get rid of that flag
         *
         * since 1.6.0 the constructor is accessed directly in many places
         * we need to avoid running the same actions many times
         *
         */
    }

    public function init()
    {
        add_action('peepso_activity_post_attachment', array(&$this, 'content_attach_media'), 20);
        add_action('peepso_activity_comment_attachment', array(&$this, 'content_attach_media'), 10);
        add_action('peepso_activity_delete', array(&$this, 'delete_post_or_comment'));
    }

    /*
     * Sets the user id to be used for queries
     * @param int $user The user id that used for queries
     */
    public function set_user_id($user)
    {
        $this->user_id = $user;
    }

    /*
     * Sets the user id considered as the owner for queries
     * @param int $owner The user id of the owner to be used for queries
     */
    public function set_owner_id($owner)
    {
        $this->owner_id = $owner;
    }

    /*
     * returns the named property value from the current result set entry
     * @param string $prop The name of the property to retrieve
     */
    public function get_prop($prop)
    {
        if (isset($this->post_data[$prop]))
            return ($this->post_data[$prop]);
        return ('');
    }

    /*
     * add a post to an activity stream
     * @param int $owner id of owner - who's Wall to place the post on
     * @param int $author id of author making the post
     * @param string $content the contents of the Activity Stream Post
     * @param array $extra additional data used to create the post
     * @return mixed The post id on success, FALSE on failure
     */
    public function add_post($owner, $author, $content, $extra = array())
    {
        // check owner's permissions
        if (PeepSo::check_permissions($owner, PeepSo::PERM_POST, $author) === FALSE) {
            return (FALSE);
        }

        // Cleaning here, because we cannot call htmlspecialchars while displaying the HTML since we'll be showing
        // some links, if there are any, on the post content.
        $content = htmlspecialchars($content);
        $content = trim(PeepSoSecurity::strip_content($content)); #4360 don't force-trim posts, validation should happen somewhere else
        //$content = substr(trim(PeepSoSecurity::strip_content($content)), 0, PeepSo::get_option('site_status_limit', 4000));
        if (PeepSo::get_option_new('remove_excessive_line_breaks')) {
            $content = preg_replace("/\n{2,}/", "\n\n", $content);
        }
        $repost = NULL;

        $rank = new PeepSoActivityRanking();
        if (!empty($extra['repost']) && $this->get_post($extra['repost']))
        {
            $repost = $this->get_repost_root($extra['repost']);
            $orig_post = $this->get_activity_post($repost);
            // Flag the reposted root for recompute; the cron credits the repost event.
            $rank->mark_dirty($orig_post->act_id);
        }

        // don't do anything if contents are empty
        if (!PeepSo3_Utilities_String::maybe_strlen($content) && NULL === $repost && !apply_filters('peepso_activity_allow_empty_content', FALSE))
            return (FALSE);

        // create post
        $aPostData = array(
            'post_title'	=> "{$owner}-{$author}-" . time(),
            'post_excerpt'  => wp_encode_emoji($content),
            'post_content'  => wp_encode_emoji($content),
            'post_status'   => 'publish',
//			'post_date'		=> gmdate('Y-m-d H:i:s'), // date('Y-m-d H:i:s'),
//			'post_date_gmt' => date('Y-m-d H:i:s'), // gmdate('Y-m-d H:i:s'),
            'post_author'   => $author,
            'post_type'		=> PeepSoActivityStream::CPT_POST
        );

        $aPostData = apply_filters('peepso_pre_write_content', array_merge($aPostData, $extra), self::MODULE_ID, __FUNCTION__);
        $content = $aPostData['post_content'];

        if(isset($extra['future'])) {

            if(is_numeric($extra['future'])) {
                $extra['future'] = $extra['future'] / 1000;
            } else {
                $extra['future'] = strtotime($extra['future']);
            }

            // Attempt to fix timezone offset
            $extra['future'] = $extra['future'] - 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());

            $aPostData['post_status'] = 'future';

            $aPostData['post_date_gmt'] = gmdate('Y-m-d H:i:s', $extra['future']);
        }

        if(isset($extra['date'])) {

            if(is_numeric($extra['date'])) {
                $extra['date'] = $extra['date'] / 1000;
            } else {
                $extra['date'] = strtotime($extra['date']);
            }

            // Attempt to fix timezone offset
            $extra['date'] = $extra['date'] - 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());

            $aPostData['post_date_gmt'] = gmdate('Y-m-d H:i:s', $extra['date']);
        }

        $id = wp_insert_post($aPostData);

        // add metadata to indicate whether or not to display link previews for this post
        add_post_meta($id, '_peepso_display_link_preview', (isset($extra['show_preview']) ? $extra['show_preview'] : 1), TRUE);

        // check $id for failure?
        if (0 === $id) {
            return (FALSE);
        }

        if(isset($extra['pin'])) {
            add_post_meta($id, 'peepso_pinned', current_time('timestamp', TRUE), TRUE);
            add_post_meta($id, 'peepso_pinned_by', get_current_user_id(), TRUE);

            if($pin_until = strtotime($extra['pin'])) {
                $pin_until = $pin_until - 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());
                add_post_meta($id, 'peepso_pinned_until', $pin_until, TRUE);

            }
        }

        // add data to Activity Stream data table
        $privacy = (isset($extra['act_access']) ? $extra['act_access'] : PeepSoUser::get_instance($author)->get_profile_accessibility());
        $aActData = array(
            'act_owner_id' => $owner,
            'act_module_id' => (isset($extra['module_id']) ? $extra['module_id'] : self::MODULE_ID),
            'act_external_id' => (isset($extra['external_id']) ? $extra['external_id'] : $id),
            'act_access' => $privacy,
            'act_ip' => PeepSo::get_ip_address(),
            'act_repost_id' => intval($repost),
        );

        $aActData = apply_filters('peepso_activity_insert_data', $aActData);

        global $wpdb;
        $res = $wpdb->insert($wpdb->prefix . self::TABLE_NAME, $aActData);

        if(!is_int($res)) {
            new PeepSoError('Unable to create activities entry: '.$wpdb->last_error.' '.var_export($aActData, TRUE));
            wp_delete_post($id);
            return FALSE;
        }

        $act_id = $wpdb->insert_id;
        $rank->add_new($act_id);
        // Seed score (creation boost for Hot) is computed on the next cron tick.
        $rank->mark_dirty($act_id);

        // #419 sticky post privacy - update only if writing on my own wall
        if($aActData['act_module_id'] == self::MODULE_ID && $owner == $author) {
            update_user_meta($author, 'peepso_last_used_post_privacy', $privacy);
        }

        /**
         * @param int The WP post ID
         * @param int The act_id
         */
        if ($repost) {
            do_action('peepso_activity_after_repost', $id, $act_id);
        } else {
            do_action('peepso_activity_after_add_post', $id, $act_id);
        }

        add_filter('oembed_result', array(&$this, 'oembed_result'), 10, 3);
        // TODO: let's run the filter on the content before adding it via the wp_insert_post() above.
        // this will remove the need for the wp_update_post().
        // Art: Post ID is required so ID is not yet ready if we need to run this filter before adding via wp_insert_post()
        $filtered_content = apply_filters('peepso_activity_post_content', $content, $id);

        remove_filter('oembed_result', array(&$this, 'oembed_result'));

        wp_update_post(array('ID' => $id, 'post_content' => $filtered_content));

        $this->save_peepso_media($id);

        $note = new PeepSoNotifications();
        // send owner an email
        if ($author !== $owner) {
            $user_owner = PeepSoUser::get_instance($owner);	// get_user_by('id', $owner_id);
            $user_author = PeepSoUser::get_instance($author);	// get_user_by('id', $author_id);
            $orig_post = get_post($id);

            $data = array(
                'permalink' => PeepSo::get_page('activity_status', FALSE) . $orig_post->post_title,
                'post_content' => $orig_post->post_content,
            );
            $data = array_merge($data, $user_author->get_template_fields('from'), $user_owner->get_template_fields('user'));

            $i18n = __('Someone posted on your profile', 'peepso-core');
            $message = 'Someone posted on your profile';
            $args = ['peepso-core'];
            PeepSoMailQueue::add_notification_new($owner, $data, $message, $args, 'wall_post', 'wall_post', PeepSoActivity::MODULE_ID);

            $i18n = __('wrote on your profile', 'peepso-core');
            $message = 'wrote on your profile';
            $args = ['peepso-core'];

            $note->add_notification_new($author, $owner, $message, $args, 'wall_post', self::MODULE_ID, $id, $act_id);
        }

        // activity_followers fallback for post owner
        $act_notif = new PeepSo3_Activity_Notifications($id, $author);
        if (!$act_notif->is_exists()) {
            $act_notif->set(1);
        }

        // Send original author a notification if content is shared
        // #2499 there is no checking for repost, user author will always automatically subscribed
        if (NULL !== $repost) {
            $orig_post = $this->get_activity_post($repost);

            $user_owner = PeepSoUser::get_instance($orig_post->post_author);
            $user_author = PeepSoUser::get_instance($author);

            // Admin can repost own posts, no need for a notification
            if($user_author != $user_owner) {
                $data = array(
                    'permalink' => PeepSo::get_page('activity_status', FALSE) . $orig_post->post_title,
                    'post_content' => $orig_post->post_content,
                );

                $data = array_merge($data, $user_author->get_template_fields('from'), $user_owner->get_template_fields('user'));

                $activity_type = array(
                    'type' => 'share',
                    'text' => __('post', 'peepso-core'));
                #$activity_type = apply_filters('peepso_notifications_activity_type', $activity_type, $orig_post->ID, $act_id);

                $i18n = __('Someone shared your post', 'peepso-core');
                $message = 'Someone shared your post';
                $args = ['peepso-core'];

                PeepSoMailQueue::add_notification_new($orig_post->post_author, $data, $message, $args, $activity_type['type'], $activity_type['type'], PeepSoActivity::MODULE_ID);


                $i18n = __('shared your post', 'peepso-core');
                $message = 'shared your post';
                $args = ['peepso-core'];

                $note->add_notification_new($owner, $orig_post->post_author, $message, $args, 'share', self::MODULE_ID, $id, $act_id);
            }
        }

        return ($id);
    }

    /*
     * adds a comment to the specified post_id
     * @param int $post_id The post id to add the comment to
     * @param int $author_id The user_id of the author adding the comment
     * @param string $content The contents of the comment to add
     * @param array $extra optional extra information for the comment
     * @return int The post id if comment is successfully added or 0 if not successful
     */
    public function add_comment($post_id, $author_id, $content, $extra = array())
    {
        $post = get_post($post_id);

        if ($post->post_type == PeepSoActivityStream::CPT_COMMENT) {
            $post = $this->get_comment($post_id)->post;
            $act = $this->get_activity_data($post->act_comment_object_id, $post->act_comment_module_id);
            $post = $this->get_activity_post($act->act_id);
        }

        if (!empty(get_post_meta($post->ID, 'peepso_disable_comments', TRUE))) {
            return FALSE;
        }

        $module_id = (isset($extra['module_id']) ? $extra['module_id'] : self::MODULE_ID);

        $act_data = $this->get_activity_data($post_id, $module_id);

        if (NULL === $act_data)
            return (FALSE);

        $orig_post = $this->get_activity_post($act_data->act_id);
        $owner_id = intval($act_data->act_owner_id);

        // check owner's permissions
        if (FALSE === PeepSo::check_permissions($owner_id, PeepSo::PERM_COMMENT, $author_id)) {
            return (FALSE);
        }

        $note = new PeepSoNotifications();

        // clean content
        $content = htmlspecialchars($content);
        $content = substr(PeepSoSecurity::strip_content($content), 0, PeepSo::get_option('site_status_limit', 4000));
        if (PeepSo::get_option_new('remove_excessive_line_breaks')) {
            $content = preg_replace("/\n{2,}/", "\n\n", $content);
        }

        // create post
        $aPostData = array(
            'post_title'	=> "{$owner_id}-{$author_id}-" . time(),
            'post_content'  => wp_encode_emoji($content),
            'post_excerpt'  => wp_encode_emoji($content),
            'post_status'   => 'publish',
            'post_author'   => $author_id,
            'post_type'		=> PeepSoActivityStream::CPT_COMMENT
        );
        $aPostData = apply_filters('peepso_pre_write_content', array_merge($aPostData, $extra), self::MODULE_ID, __FUNCTION__);

        $id = wp_insert_post($aPostData);

        // add data to Activity Stream data table
        $external_id = (isset($extra['external_id']) ? $extra['external_id'] : $id);
        $aActData = array(
            'act_owner_id' => $owner_id,
            'act_module_id' => self::MODULE_ID, // comments always belong to the activity module
            'act_external_id' => $external_id,
            'act_access' => (isset($extra['access']) ? $extra['access'] : PeepSoUser::get_instance($author_id)->get_profile_accessibility()),
            'act_ip' => PeepSo::get_ip_address(),
            'act_comment_object_id' => $act_data->act_external_id,
            'act_comment_module_id' => $module_id
        );

        global $wpdb;
        $wpdb->insert($wpdb->prefix . self::TABLE_NAME, $aActData);
        $act_id = $wpdb->insert_id;

        if ((int) $act_data->act_comment_object_id === 0) {
            $post_data = $this->get_activity_data($act_data->act_external_id, $act_data->act_module_id);
        } else {
            $post_data = $this->get_activity_data($act_data->act_comment_object_id, $act_data->act_comment_module_id);
        }

        $rank = new PeepSoActivityRanking();
        $rank->mark_dirty($post_data->act_id);

        if ( PeepSo::is_dev_mode('show_unread_posts_and_comments') ) {
            PeepSoActivityRanking::remove_unique_read($post_data->act_id);
        }

        add_filter('oembed_result', array(&$this, 'oembed_result'), 10, 3);
        $filtered_content = apply_filters('peepso_activity_post_content', $content, $id);
        remove_filter('oembed_result', array(&$this, 'oembed_result'));

        wp_update_post(array('ID' => $id, 'post_content' => $filtered_content));

        $this->handle_embed_data();
        $this->save_peepso_media($id);

        // update the post to reflect
        $wpdb->update($wpdb->prefix . self::TABLE_NAME, array('act_has_replies' => 1), array('act_external_id' => $external_id, 'act_module_id' => $module_id));

        if ($orig_post->post_type == PeepSoActivityStream::CPT_POST) {
            $orig_post = $this->get_activity_post($act_data->act_id);
            $owner_id = intval($orig_post->post_author);
        } else {
            $orig_post = $this->get_activity_post($act_data->act_id);
            $act_data = $this->get_activity_data($orig_post->ID, $module_id);
            $owner_id = intval($orig_post->post_author);
        }

        // Build the full recipient list (owner + any Page managers, excluding the commenter).
        $notify_ids = apply_filters('peepso_activity_notification_recipients', array($owner_id), $orig_post->ID, $author_id);
        $notify_ids = array_values(array_filter(array_unique(array_map('intval', $notify_ids)), function($id) use ($author_id) {
            return $id !== intval($author_id);
        }));

        // This returns parent comment in case of comment reply
        $parent_post_owner_id = $owner_id;
        $parent_post_id = $orig_post->ID;

        // It's  a comment reply
        // @TODO this still returns wrong ID if it's a comment reply to a photo in an album
        if ($orig_post->post_type != PeepSoActivityStream::CPT_POST) {
            $parent_post = $this->get_activity_data($act_data->act_comment_object_id, $act_data->act_comment_module_id);
            $parent_post_id = $parent_post->act_external_id;
            $parent_post_owner_id = $parent_post->act_owner_id;
        }

        // activity_followers fallback for post owner
        $act_notif = new PeepSo3_Activity_Notifications($parent_post_id, $parent_post_owner_id);
        if (!$act_notif->is_exists()) {
            $act_notif->set(1);
        }

        if (PeepSo::get_option_new('post_auto_follow_comment')) {
            // activity_followers  for comment author
            $act_notif = new PeepSo3_Activity_Notifications($parent_post_id, $author_id);
            if (!$act_notif->is_exists()) {
                $act_notif->set(1);
            }
        }

        $did_notify = $did_email = [];

        // send notification to post owner(s) and Page managers
        // #2499 activity and comment owner will always get the notification
        if (!empty($notify_ids)) {
            $user_author = PeepSoUser::get_instance($author_id);

            // check anonymous commenter
            $anon_op = '';
            if (PeepSo::get_option_new('postbox_anon_enabled')) {
                $anon_op = get_post_meta($parent_post_id, PeepSo3_anon::META_POST_ANON_OP, TRUE);
                if (PeepSo3_Utilities_String::maybe_strlen($anon_op) && intval($anon_op) == $author_id) {
                    $user_author = PeepSoUser::get_instance(PeepSo3_anon::get_instance()->anon_id);
                }
            }

//            $orig_post = $this->get_activity_post($act_data->act_id);

//			$data = array(
//				'email' => $user_owner->get_email(),
//				'ownername' => $user_owner->get_username(),
//				'authorname' => $user_author->get_fullname(),
//				'username' => $user_author->get_username(),
//				'post_title' => $orig_post->post_title
//			);
            // Base data shared across all recipients; 'user' fields merged per-recipient below.
            $base_url = PeepSo::get_page('activity_status', FALSE);
            $data_base = array(
                'post_content' => $orig_post->post_content,
            );
            $data_base = array_merge($data_base, $user_author->get_template_fields('from'));

            if ($orig_post->post_type == PeepSoActivityStream::CPT_POST) {

                $permalink = $base_url . $orig_post->post_title . '#comment.' . $act_data->act_id . '.' . $id . '.' . $act_id;

                // handle for photo album
                $object_id = apply_filters('peepso_activity_user_comment_object_id', $act_data->act_external_id, $act_data->act_module_id);

                foreach ($notify_ids as $notif_id) {
                    $notif_user_owner = PeepSoUser::get_instance($notif_id);

                    // anon: if this recipient is the anon identity, reveal their real user
                    if (PeepSo3_Utilities_String::maybe_strlen($anon_op) && intval(PeepSo3_anon::get_instance()->anon_id) == $notif_id) {
                        $notif_user_owner = PeepSoUser::get_instance(intval($anon_op));
                    }

                    $data = array_merge($data_base, $notif_user_owner->get_template_fields('user'));
                    $data['permalink'] = $permalink;

                    $i18n = __('Someone commented on your post', 'peepso-core');
                    $message = 'Someone commented on your post';
                    $args = ['peepso-core'];

                    $success = PeepSoMailQueue::add_notification_new($notif_id, $data, $message, $args, 'user_comment', 'user_comment', PeepSoActivity::MODULE_ID);
                    if (FALSE != $success) {
                        $did_email[] = $notif_id;
                    }

                    # 2499 notify if only user is following; extra recipients (managers) always notified
                    $is_primary = ($notif_id === $owner_id);
                    $act_notif = new PeepSo3_Activity_Notifications($object_id, $notif_id);
                    if (!$is_primary || $act_notif->is_following()) {
                        $i18n = __('commented on your post', 'peepso-core');
                        $message = 'commented on your post';
                        $args = ['peepso-core'];

                        $success = $note->add_notification_new($author_id, $notif_id, $message, $args, 'user_comment', self::MODULE_ID, $id, $act_id);
                        if (FALSE != $success) {
                            $did_notify[] = $notif_id;
                        }
                    }
                }
            } else {
                $act_data_parent = $this->get_activity_data($act_data->act_comment_object_id, $act_data->act_comment_module_id);
                $act_post_parent = get_post($act_data->act_comment_object_id);

                $permalink = $base_url . $act_post_parent->post_title . '#comment.' . $act_data_parent->act_id . '.' . $orig_post->ID . '.' . $act_data->act_id . '.' . $external_id;

                foreach ($notify_ids as $notif_id) {
                    $notif_user_owner = PeepSoUser::get_instance($notif_id);

                    if (PeepSo3_Utilities_String::maybe_strlen($anon_op) && intval(PeepSo3_anon::get_instance()->anon_id) == $notif_id) {
                        $notif_user_owner = PeepSoUser::get_instance(intval($anon_op));
                    }

                    $data = array_merge($data_base, $notif_user_owner->get_template_fields('user'));
                    $data['permalink'] = $permalink;

                    $i18n = __('Someone replied to your comment', 'peepso-core');
                    $message = 'Someone replied to your comment';
                    $args = ['peepso-core'];

                    $success = PeepSoMailQueue::add_notification_new($notif_id, $data, $message, $args, 'user_reply_comment', 'stream_reply_comment', PeepSoActivity::MODULE_ID);
                    if (FALSE != $success) {
                        $did_email[] = $notif_id;
                    }

                    $i18n = __('replied to your comment', 'peepso-core');
                    $message = 'replied to your comment';
                    $args = ['peepso-core'];

                    $success = $note->add_notification_new($author_id, $notif_id, $message, $args, 'stream_reply_comment', self::MODULE_ID, $id, $act_id);
                    if (FALSE != $success) {
                        $did_notify[] = $notif_id;
                    }
                }
            }

        }

        $users = $this->get_comment_users($post_id, $module_id);

        $skip = array_merge(array($author_id), $notify_ids);

        $i18n = __('commented on a post you are following', 'peepso-core');
        $message = 'commented on a post you are following';
        $args = ['peepso-core'];

        while ($users->have_posts()) {

            $users->next_post();

            if(in_array($users->post->post_author, $skip)) {
                continue;
            }

            $skip[] = $users->post->post_author;
            $act_notif = new PeepSo3_Activity_Notifications($parent_post_id, $users->post->post_author);

            if ($act_notif->is_following() && !in_array($users->post->post_author, $did_notify)) {
                $success = $note->add_notification_new($author_id, $users->post->post_author, $message, $args, 'user_comment', self::MODULE_ID, $id, $act_id);

                if(FALSE != $success) {
                    $did_notify[] = $users->post->post_author;
                }
            }
        }

        // #2499 send notification for post followers
        $act_notif = new PeepSo3_Activity_Notifications($post_id, $author_id);
        $followers = $act_notif->get_followers();
        foreach ($followers as $follower) {
            if (!in_array($follower, $skip)) {
                $act_notif = new PeepSo3_Activity_Notifications($post_id, $follower);
                if ($act_notif->is_following() && !in_array($follower, $did_notify)) {
                    $success = $note->add_notification_new($author_id, $follower, $message, $args, 'user_comment', self::MODULE_ID, $id, $act_id);

                    if(FALSE != $success) {
                        $did_notify[] = $follower;
                    }
                }
            }
        }

        do_action('peepso_after_add_comment', $id, $act_id, $did_notify, $did_email);

        return ($id);
    }

    public static function store_revision($post_id, $content_after, $deleted = FALSE) {
        global $wpdb;

        $content_before = get_post($post_id);
        $content_before = $content_before->post_content;

        $wpdb->insert($wpdb->prefix.'peepso_revisions', array(
            'post_id'        => $post_id,
            'user_id'        => get_current_user_id(),
            'content_before' => $content_before,
            'content_after'  => $content_after,
        ));

        update_post_meta($post_id,'peepso_last_edit', current_time('timestamp', TRUE));
    }

    /*
     * Saves a comment after editing
     * @param PeepSoAjaxResponse $resp The response object
     */
    public function savecomment(PeepSoAjaxResponse $resp)
    {
        $post_id = $this->_input->int('postid');
        $owner_id = $this->get_author_id($post_id);
        $user_id = $this->_input->int('uid');
        $post_content = $this->_input->value('post', '', FALSE); // SQL safe.

        global $post;
        $post = $this->get_comment($post_id);
        $post = $post->next_post();

        // don't allow empty comments
        if (empty($post_content) && !apply_filters('peepso_activity_allow_empty_comment', FALSE)) {
            $resp->success(0);
            $resp->notice(__('Comment is empty', 'peepso-core'));
            return;
        }

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            $post_content = substr(PeepSoSecurity::strip_content($post_content), 0, PeepSo::get_option('site_status_limit', 4000));
            add_filter('oembed_result', array(&$this, 'oembed_result'), 10, 3);
            $filtered_content = apply_filters('peepso_activity_post_content', $post_content, $post_id);
            remove_filter('oembed_result', array(&$this, 'oembed_result'));

            $data = apply_filters('peepso_pre_write_content', array(
                'post_content' => wp_encode_emoji($filtered_content),
                'post_excerpt' => wp_encode_emoji($post_content),
                'post_modified' => current_time('mysql'),
                'post_modified_gmt' => current_time('mysql', TRUE)
            ), self::MODULE_ID, __FUNCTION__);
            global $wpdb;
            $wpdb->update($wpdb->posts, $data, array('ID' => $post_id));
            $_post = $this->get_activity_data($post_id);

            PeepSoActivity::store_revision($post_id, $filtered_content);

            /**
             * @param int post_id
             * @param object activity
             */
            do_action('peepso_activity_after_save_comment', $post_id, $_post);

            if (empty($_post->act_repost_id)) {
                $this->handle_embed_data();
                $this->save_peepso_media($post_id);
            }

            $this->get_comment($post_id);
            $this->next_comment();

            $html = $this->content(NULL, FALSE);

            ob_start();
            $this->comment_attachment();
            $resp->success(1);
            $resp->set('html', $html);
            $resp->set('attachments', ob_get_clean());

            ob_start();
            $this->comment_actions();
            $resp->set('actions', ob_get_clean());

            ob_start();
            $this->comment_actions_dropdown();
            $resp->set('actions_dropdown', ob_get_clean());
        }
    }

    /**
     * Allows user to edit a comment.
     * @param PeepSoAjaxResponse $resp The AJAX Response instance.
     */
    public function editcomment(PeepSoAjaxResponse $resp)
    {
        $post_id = $this->_input->int('postid');
        $user_id = $this->_input->int('uid');
        $owner_id = intval($this->get_author_id($post_id));

        $this->set_user_id($user_id);
        $this->set_owner_id($user_id);
        $wpq = $this->get_comment($post_id);
        $this->next_comment();
        global $post;

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            $data = array('cont' => $post->post_excerpt, 'post_id' => $post_id);
            $html = PeepSoTemplate::exec_template('activity', 'comment-edit', $data, TRUE);

            $resp->set('html', $html);
            $resp->success(1);
        } else {
            $resp->success(0);
            $resp->error(__('You do not have permission to edit this post.', 'peepso-core'));
        }
    }

    /*
     * deletes a post and all associated hides, likes, and child comments/posts
     * @param int $post_id the post identifier
     */
    public function delete_post($post_id)
    {
        global $wpdb;

        // find all comments/child posts of the specified post id
        $sql = "SELECT `ID`
				FROM `{$wpdb->posts}`
				WHERE `post_parent`=%d AND `post_type`=%s";
        $all_posts = $wpdb->get_col($wpdb->prepare($sql, $post_id, PeepSoActivityStream::CPT_COMMENT));

        $all_posts = array();

        $post = $this->get_post($post_id);
        if ($post->have_posts()) {
            $post->the_post();
            $this->post_data = get_object_vars($post->post);

            if ($this->has_comments())
                while ($this->next_comment())
                    $all_posts[] = $this->comment_data['ID'];
        }
        // add the specified post_id to the list
        $all_posts[] = intval($post_id);

        // delete all of the posts in the list
        foreach ($all_posts as $postid) {
            $post_query = $this->get_post($postid);

            // let any add-ons know about the delete
            do_action('peepso_delete_content', $postid);

            if (FALSE === $post_query->have_posts()) {
                $sql = "DELETE FROM `{$wpdb->prefix}" . self::TABLE_NAME . "` WHERE `act_external_id`=%d AND `act_module_id`=%d";
                $wpdb->query($wpdb->prepare($sql, $postid, self::MODULE_ID));
            } else {
                $post = $post_query->post;

                # 4543 / #8113 if PeepSo specific data is missing, avoid deleting random posts.
                # The old guard `isset($post->act_id) || 0 == $post->act_id` was a tautology
                # (0 == null is true), so an activity-less post took the repost branch with
                # act_id coerced to 0 — and `act_repost_id`=0 matches EVERY non-repost row,
                # deleting all module-1 activities (every comment site-wide) in one query.
                if(!empty($post->act_id)) {
                    $sql = "DELETE FROM `{$wpdb->prefix}" . self::TABLE_NAME . "` WHERE (`act_external_id`=%d OR `act_repost_id`=%d) AND `act_module_id`=%d";
                    $sql = $wpdb->prepare( $sql, $postid, $post->act_id, self::MODULE_ID );
                } else {
                    $sql = "DELETE FROM `{$wpdb->prefix}" . self::TABLE_NAME . "` WHERE (`act_external_id`=%d) AND `act_module_id`=%d";
                    $sql = $wpdb->prepare( $sql, $postid, self::MODULE_ID );
                }

                $wpdb->query($sql);
            }

            $sql = "DELETE FROM `{$wpdb->prefix}" . PeepSoLike::TABLE . "` " .
                " WHERE `like_module_id`=%d AND `like_external_id`=%d ";
            $wpdb->query($wpdb->prepare($sql, self::MODULE_ID, $postid));

            $sql = "DELETE FROM `{$wpdb->prefix}" . PeepSoReport::TABLE . "` WHERE `rep_external_id`=%d ";
            $wpdb->query($wpdb->prepare($sql, $postid));

            $sql = "DELETE FROM `{$wpdb->prefix}" . PeepSoNotifications::TABLE . "` WHERE `not_external_id`=%d ";
            $wpdb->query($wpdb->prepare($sql, $postid));

            wp_delete_post($postid, TRUE);
        }

    }


    /*
     * get the the peepso_activities data associated with a given post
     * @param int $act_external_id the act_external id of the record to retrieve
     * @return Object The data record on sucess or FALSE if not found.
     */
    public function get_activity_data($act_external_id, $module_id = self::MODULE_ID)
    {
        global $wpdb;

        $sql = 'SELECT * ' .
            " FROM `{$wpdb->prefix}" . self::TABLE_NAME . '` ' .
            ' WHERE `act_external_id`=%d AND `act_module_id`=%d' .
            ' ORDER BY `act_id` DESC LIMIT 1 ';
        // @todo: remove order by to prevent wrong activities for picso
        $ret = $wpdb->get_row($wpdb->prepare($sql, $act_external_id, $module_id));

        return ($ret);
    }

    /*
     * get the the peepso_activities data associated with a given post
     * @param int $act_external_id the act_external id of the record to retrieve
     * @param int $act_owner_id the act_owner_id id of the record to retrieve
     * @return Object The data record on sucess or FALSE if not found.
     */
    public function get_activity_data_by_owner($act_external_id, $act_owner_id=0, $module_id = self::MODULE_ID)
    {
        global $wpdb;

        $sql = 'SELECT * ' .
            " FROM `{$wpdb->prefix}" . self::TABLE_NAME . '` ' .
            ' WHERE `act_owner_id` = %d AND `act_external_id`=%d AND `act_module_id`=%d' .
            ' ORDER BY `act_id` DESC LIMIT 1 ';

        $ret = $wpdb->get_row($wpdb->prepare($sql, $act_owner_id, $act_external_id, $module_id));

        return ($ret);
    }

    /**
     * Return the WP_Post object that is related to an activity object, usually the parent post of a media object.
     * @param  int $act_id Activity's post Id to get
     * @return mixed Returns NULL if $act_id not found or no post; othewise returns WP_Post object
     */
    public function get_activity_post($act_id)
    {
        $act_data = $this->get_activity($act_id);

        // added check to get rid of "Trying to get property of non-object" errors
        if (!is_object($act_data))
            return (NULL);

        $id = apply_filters('peepso_activity_post_id', $act_data->act_external_id, $act_data);

        $this->owner_id = $this->user_id = get_current_user_id();

        # since PeepSo/PeepSo#3599 edit future post capability
        $args = array(
            'p' => $id,
            'post_status' => array('publish', 'pending', 'future'), # since PeepSo/peepso#1935 fix error notice for non admin user
            'post_type' => apply_filters('peepso_activity_post_types', array(PeepSoActivityStream::CPT_POST, PeepSoActivityStream::CPT_COMMENT)),
            '_bypass_permalink' => TRUE,
            '_bypass_pinned' => TRUE
        );

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        $post = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);

        if ($post->have_posts())
            return ($post->post);
        return (NULL);
    }

    /*
     * Get the the peepso_activities data associated by ID
     * @param int $act_id the act_id of the record to retrieve
     * @return Object The data record on sucess or NULL if not found.
     */
    public function get_activity($act_id)
    {
        // TODO: this appears to be called four times while outputting a single post to the Activity Stream. Let's try to reduce that!
        // Art: I checked this and it only called once per $act_id
        global $wpdb;

        $sql = 'SELECT * ' .
            " FROM `{$wpdb->prefix}" . self::TABLE_NAME . '` ' .
            ' WHERE `act_id`=%d LIMIT 1 ';
        $ret = $wpdb->get_row($wpdb->prepare($sql, $act_id));

        // TODO: this is failing sometimes, returnning NULL. The code that calls this needs to check the result and recover, otherwise it throws "Trying to get property of non-object" errors
        // TODO: note: this is failing when the requesting user is not the same as the posting user on a reposted item.
        return ($ret);
    }

    /*
     * Retrieve a single Activity Stream post by id
     * @param int $id The post id of the Activity Stream post to retrieve
     * @param int $owner_id The user_id of the owner of the post to retrieve
     * @param int $user_id The user_id of the user retrieving the post
     * @param bool $bypass_permalink Whether or not to bypass the permalink queries in filter_post_clauses,
     *                               useful if we want to call this function without worrying about the link.
     * @return WP_Query instance of WP_Query that contains the post
     */
    public function get_post($id, $owner_id = NULL, $user_id = NULL, $bypass_permalink = FALSE)
    {
        $this->owner_id = $this->user_id = 0;

        if (NULL === $owner_id) {
            // use the current user's id
            $user = get_current_user_id();
        } else
            $user = $owner_id;
        $this->owner_id = $user;

        if (NULL === $user_id) {
            // use the current user'd id
            $user = get_current_user_id();
        } else
            $user = $user_id;
        $this->user_id = $user;

        $args = array(
            'p' => $id,
            'post_type' => PeepSoActivityStream::CPT_POST,
            'post_status' => array('publish', 'future', 'pending'),
//			'posts_per_page' => 1,
            '_bypass_permalink' => $bypass_permalink,
            '_bypass_pinned' => TRUE
        );

        if (NULL !== $owner_id) {
            $this->owner_id = $owner_id;
        }

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        $this->post_query = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);

        return ($this->post_query);
    }

    /**
     * Returns an object representing all WP data and PeepSo data related to the post
     *
     * It should be enough to get act_id, module_id, external_id and all the other stuff
     */
    public function get_post_object($post_id) {
        $this->get_post($post_id);
        if ($this->post_query->have_posts()) {
            $this->post_query->the_post();
            return $this->post_query->post;
        }

        return NULL;
    }

    /*
     * Return the number of ActivityStream posts created by this user
     * @param int $user_id The user id to count posts from
     * @return int The number of posts by that user
     */
    public function get_posts_by_user($user_id)
    {
        global $wpdb;

        $sql = 'SELECT COUNT(*) AS `count` ' .
            " FROM `{$wpdb->posts}` " .
            ' WHERE `post_author`=%d AND `post_type`=%s AND `post_status`=\'publish\' ';
        $res = $wpdb->get_var($wpdb->prepare($sql, $user_id, PeepSoActivityStream::CPT_POST));
        return ($res);
    }


    /*
     * return a WP_Query instance for a user's activity stream posts
     * @param int $offset The number of posts to offset the query by
     * @param int $owner_ud The user_id of of the owner of the posts to be queried. If NULL will get posts from all users.
     * @param int $user_id The user_id value of the Activity Stream items to view
     * @param int $paged The page to display if opting to paginate
     * @return WP_Query instance of queried Activity Stream data
     */
    public function get_posts($offset = NULL, $owner_id = NULL, $user_id = NULL, $paged = NULL, $pinned = FALSE, $limit = 1, $post_id = 0, $search = NULL, $search_mode = 'exact')
    {
        $this->owner_id = $this->user_id = 0;

        if (NULL === $owner_id) {
            // use the current user's id
            $user = get_current_user_id();
        } else {
            $user = $owner_id;
        }

        $this->owner_id = $user;

        if (NULL === $user_id) {
            // use the current user'd id
            $user = get_current_user_id();
        } else {
            $user = $user_id;
        }

        $this->user_id = $user;

        if( NULL == $limit ) {
            $limit = 1;
        }

        if (PeepSo::get_option_new('order_post_asc')) {
            $order = 'ASC';
        } else {
            $order = 'DESC';
        }

        $args = array(
            'post_type' => $this->query_type = PeepSoActivityStream::CPT_POST,
            'orderby' => 'post_date_gmt',
            'order' => $order,
            'posts_per_page' => $limit,
            'paged' => (NULL === $paged ? NULL : $paged),   // used to default to zero
            'offset' => (NULL === $offset ? NULL : $offset),// used to default to zero
        );

        if (PeepSo::get_option_new('remove_sql_cal_found_rows_enable')) {
            $args['no_found_rows'] = true; // Disables SQL_CALC_FOUND_ROWS
        }

        if(TRUE == $pinned) {
            $args['pinned'] = TRUE;
        }

        if(0 < $post_id) {
            $args['p'] = $post_id;
        }

        // Negative page number indicates we want all posts newer than a given ID
        if($paged < 0) {
            $args['posts_per_page'] = -1;
            unset($args['paged']);
            unset($args['offset']);
            $args['post__not_in'] = range(1, abs($paged));
        }

        $show_my_posts_list =  apply_filters( 'peepso_show_my_posts_list', [] );
        reset($show_my_posts_list);
        $default = key($show_my_posts_list);
        if('hide_mine'==$this->_input->value('stream_filter_show_my_posts', $default, array_keys($show_my_posts_list))) {
            $args['author__not_in'] = array(get_current_user_id());
        }

        $default = PeepSo::get_option_new('stream_sort_default');
        $sort_posts =  apply_filters( 'peepso_stream_sort_list', [] );
        $sort_default = PeepSo::get_option_new('stream_sort_default');
        if('new_and_commented' == $this->_input->value('stream_filter_sort_by', $sort_default, array_keys($sort_posts))) {
            $args['orderby'] = 'post_modified_gmt';
        }

        // extends the clause so, other plugins like groups / page / event can filters the activity
        $args = apply_filters('peepso_activity_meta_query_args', $args, $this->stream_id);

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        if ( PeepSo::is_dev_mode('experimental_fast_queries') ) {
            $args['suppress_filters'] = FALSE;
            add_filter('posts_request', function ($query) use ($args) {
                return $this->filter_posts_request($query, $args);
            });
        }

        if('core_scheduled' == $this->stream_id) {
            $args['post_status'] = 'future';
            if(!PeepSo::is_admin()) {
                $args['author'] = get_current_user_id();
                unset($args['author__not_in']);
            }
        }

        if ($this->context == NULL) {
            $args['meta_query'] = [
                [
                    'key' => PeepSo3_Anon::META_POST_ANON_OP,
                    'compare' => 'NOT EXISTS',
                ],
            ];
        }

        // #6046 handle post status for single context view
        if ('single' == $this->context) {
            if (isset($args['post_status'])) {
                if (is_array($args['post_status'])) {
                    $args['post_status'][] = 'publish';
                    $args['post_status'][] = 'pending';
                    $args['post_status'] = array_unique($args['post_status']);
                } else if (!empty($args['post_status'])) {
                    $args['post_status'] = array_unique($args['post_status'], 'publish', 'pending');
                }
            } else {
                $args['post_status'] = array('publish', 'pending');
            }
        }

        if('core_reported' == $this->stream_id && PeepSo::is_admin()) {
            $args['post_status'] = array('publish', 'pending');

            // post type peepso-post or peepso-comment
            $args['post_type'] = ['peepso-post', 'peepso-comment'];

            $args['meta_query'] = [
                [
                    'key' => 'peepso_reported',
                    'value' => 1,
                    'compare' => '=',
                ],
            ];
        }

        if ('core_community' == $this->stream_id && PeepSo::is_admin()) {
            $args['_exclude_pending'] = TRUE;
            $args['post_status'] = ['publish'];
        }

        // prevent duplicate activity on wordpress.com
        if (class_exists('Advanced_Post_Cache')) {
            wp_cache_flush();
        }

        do_action('peepso_before_get_posts');
        $this->post_query = new WP_Query($args);

        if ( PeepSo::is_dev_mode('experimental_fast_queries') ) {
            remove_filter('posts_request', function(){});
        }
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);

        return ($this->post_query);
    }


    function filter_posts_request($query, $args)
    {
        $post_id = isset($args['p']) ? $args['p'] : 0;
        if ((strpos($query, PeepSoActivityStream::CPT_POST) === FALSE && strpos($query, PeepSoActivityStream::CPT_COMMENT) === FALSE) || $post_id > 0) {
            return $query;
        }
        global $wpdb;

        if (strpos($query, PeepSoActivityStream::CPT_COMMENT) !== FALSE) {
            $post_type = PeepSoActivityStream::CPT_COMMENT;
        } else {
            $post_type = PeepSoActivityStream::CPT_POST;
        }

        $query_parts = explode('LIMIT', $query);
        if ($post_type == PeepSoActivityStream::CPT_COMMENT && isset($query_parts[1])) {
            $query = $query_parts[0] . ' LIMIT ' . $query_parts[1];
        }  else if (strpos($query, 'INNER JOIN') === FALSE) {
            $query = $query_parts[0] . ' LIMIT 1';
        }

        if ($args['posts_per_page'] > 0 && $post_type == PeepSoActivityStream::CPT_POST) {
            $limit = 'LIMIT ' . ($args['paged'] - 1) . ',100';
        } else {
            $limit = '';
        }

        // @todo: improve original functions in plugin files so they can be used without adding more logic
        $where = [];
        $where[] = "$wpdb->posts.post_type = '" . $post_type . "' AND ( $wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'private' OR $wpdb->posts.post_status = 'future')";

        $group_id = $this->_input->int('group_id', 0);
        if ($group_id != 0 && $post_type != PeepSoActivityStream::CPT_COMMENT) {
            $where[] = $wpdb->prepare(" $wpdb->posts.ID IN (SELECT post_id FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value=%d) ", array('peepso_group_id', $group_id));
        }

        // regular search
        $search = $this->_input->value('search', NULL, FALSE); // SQL safe. optional, string to search
        $search_mode = $this->_input->value('search_mode', 'exact', array('exact','any')); // optional, whether to use exact phrase or any word

        $search_qry = [];
        if(NULL !== $search && FALSE === strpos($query, PeepSoActivityStream::CPT_COMMENT )) {
            if('any' == $search_mode) {
                $search = explode(' ', $search);
                foreach ($search as $key) {
                    $search_qry[] = " (`{$wpdb->posts}`.`post_content` LIKE '%$key%') ";
                }
            } else {
                $search_qry[] = " (`{$wpdb->posts}`.`post_content` LIKE '%$search%') ";
            }

            $where[] = " (". implode(' OR ', $search_qry) .")";
        }

        // hashtag search
        $hashtag = $this->_input->value('search_hashtag', '', FALSE); // SQL safe.
        if (!empty($hashtag) && (FALSE === strpos($query, PeepSoActivityStream::CPT_COMMENT))) {
            $hashtag = function_exists('mb_strtolower') ? mb_strtolower($hashtag, 'UTF-8') : strtolower($hashtag);
            $where[] = PeepSo::hashtags_query_where($hashtag);
        }

        $user_id = $this->_input->int('user_id');
        $left_join = '';
        if ($user_id) {
            $left_join = " LEFT JOIN `{$wpdb->prefix}peepso_activities` `act` ON `act`.`act_external_id` = `$wpdb->posts`.`ID`";
            $where[] = "`act`.`act_owner_id` = " . $user_id;
        }

        // merge where condition
        $where = implode(' AND ', $where);

        $query_replacement = " FROM (SELECT $wpdb->posts.* FROM $wpdb->posts" .
            $left_join .
            " WHERE $where" .
            " ORDER BY $wpdb->posts.post_date DESC $limit) $wpdb->posts";

        $query = trim($query);
        // $query = str_replace('SQL_CALC_FOUND_ROWS', '', $query);
        $query = str_replace('FROM ' . $wpdb->posts, $query_replacement, $query);

        return $query;
    }



    /*
     * Retrieves a single Activity Stream comment by id
     * @param int $post_id The comment id of the Activity Stream comment to retrieve
     * @return WP_Query instance of WP_Query that contains the comment
     */
    public function get_comment($post_id)
    {
        $this->owner_id = $this->user_id = 0;

        $args = array(
            'p' => $post_id,
            'post_type' => PeepSoActivityStream::CPT_COMMENT,
//			'post_parent' => $post_id,
//			'posts_per_page' => 1,
        );

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        $this->is_comment_query = TRUE;
        $this->comment_query = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);
        $this->is_comment_query = FALSE;

        return ($this->comment_query);
    }


    /*
     * return comments for a given post_id
     * @param int $post_id The post id to find comments for
     * @param int $offset The number of posts to offset the query by
     * @param int $module_id The module ID to match the $post_id belongs to
     * @return WP_Query instance of the queried Activity Stream Comment data
     */
    public function get_comments($post_id, $offset = NULL, $paged = 1, $limit = NULL, $module_id = self::MODULE_ID, $modal=false)
    {
//		$this->owner_id = $this->user_id = 0;

        if (NULL === $limit) {
            $comments_var = $this->get_comments_var($post_id);
            $limit = $comments_var['comments_to_display'];
        }

        $args = array(
            'post_type' => $this->query_type = PeepSoActivityStream::CPT_COMMENT,
            'order_by' => 'post_date_gmt',
            'order' => 'ASC',
            'posts_per_page' => $limit,
            'offset' => (NULL === $offset ? 0 : $offset),
            '_comment_object_id' => $post_id,
            '_comment_module_id' => $module_id,
            '_is_modal' => $modal,
        );

        if (PeepSo::get_option_new('remove_sql_cal_found_rows_enable')) {
            $args['no_found_rows'] = true; // Disables SQL_CALC_FOUND_ROWS
        }

        if (0 !== $paged && is_int($paged))
            $args['paged'] = $paged;

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses_comments'), 20, 2);
        if(PeepSo::is_admin()) { $args['post_status'] = ['publish','pending']; }
        $this->is_comment_query = TRUE;
        $this->comment_query = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses_comments'), 20);
        $this->is_comment_query = FALSE;

        #echo "<pre>";var_dump($this->comment_query);echo "</pre>";
        return ($this->comment_query);
    }

    public function count_comments($post_id, $module_id = self::MODULE_ID, $modal=false)
    {
        $args = array(
            'post_type' => $this->query_type = PeepSoActivityStream::CPT_COMMENT,
            'posts_per_page' => -1,
            'no_found_rows'  => true,
            '_comment_object_id' => $post_id,
            '_comment_module_id' => $module_id,
            '_is_modal' => $modal,
        );

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses_comments'), 20, 2);
        if(PeepSo::is_admin()) { $args['post_status'] = ['publish','pending']; }
        $this->is_comment_query = TRUE;
        $this->count_comment_query = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses_comments'), 20);
        $this->is_comment_query = FALSE;

        return ($this->count_comment_query->post_count);
    }


    /*
     * Checks whether the comment query has any remaining posts
     * @return boolean TRUE if there are more comments in the query
     */
    public function has_comments()
    {
        if (NULL === $this->comment_query) {
            $post_id = isset($this->post_data['ID']) ? intval($this->post_data['ID']) : 0;
            // #8113 fall back to the activity module id, never ''. An activity-less post
            // (LEFT JOIN yields a NULL act_module_id) otherwise passed '' to get_comments(),
            // and filter_post_clauses_comments() drops the object-id scoping when the module
            // is empty — so get_comments() returned the N most recent comments site-wide and
            // delete_post() then deleted those unrelated rows.
            $act_module_id = !empty($this->post_data['act_module_id']) ? $this->post_data['act_module_id'] : self::MODULE_ID;
            $this->get_comments($post_id, NULL, 1, NULL, $act_module_id);
        }

        if ($this->comment_query->have_posts()) {
            return (TRUE);
        }

        // Need to reset comment query when no posts are found to ensure that the next run
        // updates $this->comment_query
        $this->comment_query = NULL;
        return (FALSE);
    }


    /*
     * sets up the next post from the result set to be used with the templating system
     * @return Boolean TRUE on success with a valid post ready; FALSE otherwise
     */
    public function next_comment()
    {
        if ($this->comment_query->have_posts()) {
            if ($this->comment_query->current_post >= $this->comment_query->post_count)
                return (FALSE);

            $this->comment_query->the_post();
            $this->comment_data = get_object_vars($this->comment_query->post);
            $this->comment_data['human_friendly'] = PeepSo3_Utilities_String::maybe_strlen($human_friendly = get_post_meta($this->comment_data['ID'], 'peepso_human_friendly', TRUE)) ? $human_friendly : FALSE;

            $this->comment_data['reported'] = 0;
            $this->comment_data['reports'] = [];

            if(PeepSo::is_admin()) {
                $this->comment_data['reports'] = (new PeepSoReport())->get_reports( $this->comment_data['ID'], $this->comment_data['act_module_id'], 0);
                if(is_array($this->comment_data['reports']) && count($this->comment_data['reports'])) {
                    $this->comment_data['reported'] = count($this->comment_data['reports']);
                }
            }
            return (TRUE);
        }
        $this->comment_query = NULL;
        return (FALSE);
    }

    /**
     * Show number of recent comments based on "site_activity_comments" setting
     * @return void
     */
    public function show_recent_comments($modal=FALSE)
    {
        if (empty($this->post_data['act_module_id'])) {
            $this->post_data['act_module_id'] = PeepSoActivity::MODULE_ID;
        }

        global $post;
        $comments_var = $this->get_comments_var($post);
        $site_activity_comments = $comments_var['comments_to_display'];
        $this->post_data = get_object_vars($post);

        add_filter('posts_clauses_request', array(&$this, 'filter_last_rows'), 10, 2);
        $this->get_comments($this->post_data['ID'], NULL, 1, NULL, $this->post_data['act_module_id'], $modal);
        add_filter('posts_clauses_request', array(&$this, 'filter_last_rows'), 10, 2);
        $improve_query = PeepSo::get_option_new('remove_sql_cal_found_rows_enable');
        if ($improve_query) {
            $this->comment_query->found_posts = $this->count_comments($this->post_data['ID'], $this->post_data['act_module_id'], $modal);
            $this->comment_query->max_num_pages = $comments_var['comments_to_display'] > 0 ? ceil($this->comment_query->found_posts / $comments_var['comments_to_display']) : 0;
        }
        if ($this->comment_query->max_num_pages > 1 || ($this->comment_query->found_posts > 0 && 0 == $site_activity_comments)) {
            PeepSoTemplate::exec_template('activity', 'comment-header', array('PeepSoActivity' => $this));
        }

        // TODO: in get_comments() there is: 'order' => 'ASC' - can we change this to 'DESC' and remove the array_reverse() or the filter above??
        // Reverse the array so we get them in ASC order, skips out on building some SQL
        if (isset($this->comment_query->posts)) {
            $this->comment_query->posts = array_reverse($this->comment_query->posts);
        }

        if ($site_activity_comments > 0) {
            while ($this->next_comment()) {
                $this->show_comment();
            }
        }

        // reset comment_query
        $this->comment_query = NULL; // Reset because this only takes the latest comments, some functions may require all comments

        PeepSoTemplate::exec_template('activity', 'comment-footer');
    }

    /**
     * Get the last rows of a WP_Query filter first
     * @param array $clauses array holding the clauses for the SQL being built
     * @param WP_Query $query Query instance
     * @return array The modified array of clauses
     */
    public function filter_last_rows($clauses, $query)
    {
        global $wpdb;

        $clauses['orderby'] = "`{$wpdb->posts}`.`post_date` DESC";

        return ($clauses);
    }

    /*
     * Obtain remaining comments for display in Activity Stream
     * @param PeepSoAjaxResponse $resp The AJAX response objects
     * @output JSON encoded data of the remainin comments for the post
     */
    public function show_previous_comments(PeepSoAjaxResponse $resp)
    {
        $this->first_post_id = $this->_input->int('first', NULL);
        if (!empty($this->first_post_id)) {
            add_filter('posts_where', array(&$this, 'filter_before_id'));
        }

        $activity = $this->get_activity($this->_input->int('act_id'));
        $post = get_post($activity->act_external_id);

        $all_comments = intval($this->_input->int('all', 0));
        if ($all_comments == 1) {
            $comments_batch = -1;
        } else {
            $comments_var = $this->get_comments_var($post);
            $comments_batch = $comments_var['comments_batch'];
        }

        add_filter('peepso_user_profile_id', array(&$this, 'ajax_get_profile_id'));

        if ($this->first_post_id !== NULL) {
            add_filter('posts_clauses_request', array(&$this, 'filter_last_rows'), 10, 2);
        }

        $this->get_comments($activity->act_external_id, NULL, 1, $comments_batch, $activity->act_module_id);
        $improve_query = PeepSo::get_option_new('remove_sql_cal_found_rows_enable');
        if ($improve_query) {
            $this->comment_query->found_posts = $this->count_comments($activity->act_external_id, $activity->act_module_id, false);
            $this->comment_query->max_num_pages = $comments_var['comments_to_display'] > 0 ? ceil($this->comment_query->found_posts / $comments_var['comments_to_display']) : 0;
        }

        remove_filter('posts_where', array(&$this, 'filter_before_id'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_last_rows'));

        if ($this->first_post_id !== NULL) {
            // TODO: in get_comments() there is: 'order' => 'ASC' - can we change this to 'DESC' and remove the array_reverse() or the filter above??
            // Reverse the array so we get them in ASC order, skips out on building some SQL
            $this->comment_query->posts = array_reverse($this->comment_query->posts);
        }

        ob_start();

        while ($this->comment_query->have_posts()) {
            $this->next_comment();
            $this->show_comment();
        }

        $comments_html = ob_get_clean();

        $resp->success(1);
        $resp->set('html', $comments_html);

        if ($all_comments == 0) {
            $comment_count = intval($this->comment_query->found_posts) - $comments_batch;
            $comment_count = $comment_count > $comments_batch ? $comments_batch : $comment_count;

            $resp->set('comments_remain', $comment_count);
            $resp->set('comments_remain_caption', $this->get_comment_or_reply_label(__('Show %d more %s', 'peepso-core'), $post->post_type, $comment_count));
        }

    }

    /*
     * Display the 'Show More Posts' link
     */
    public function show_more_posts_link()
    {
    }

    /**
     * Get X number for more comments
     */
    public function get_number_of_more_comments()
    {
        // get max of comments to display
        global $post;

        $comments_var = $this->get_comments_var($post);
        $site_activity_comments = $comments_var['comments_to_display'];
        $limit = $comments_var['comments_batch'];

        // get found comments post
        $found = intval($this->comment_query->found_posts);
        $remain = intval($found - $site_activity_comments);

        if (($remain < $limit && $remain > 0) || $limit == 0) {
            return $remain;
        }

        return $limit;
    }

    /*
     * Display the 'Show All Comment' link
     */
    public function show_more_comments_link()
    {
        global $post;

        // this is only called when there are comments now; can remove has_comments() check
        //    echo esc_attr__('Show All Comments', 'peepso-core');

        global $post;
        $comments_var = $this->get_comments_var($post);
        $site_activity_comments = $comments_var['comments_to_display'];

        if ($this->comment_query->max_num_pages > 1 || ($this->comment_query->found_posts > 0 && 0 == $site_activity_comments)) {
            $comment_count = $this->get_number_of_more_comments();
            $label = __('Show %d more %s', 'peepso-core');
            if ($this->comment_query->query['offset'] == 0) {
                $label = __('Show %d %s', 'peepso-core');
            }
            echo esc_attr($this->get_comment_or_reply_label($label, $post->post_type, $comment_count));
        } else if ($site_activity_comments <= $this->comment_query->post_count) {
            if ($this->comment_query->post_count > 1) {
                $comment_count = $this->comment_query->post_count;
                echo esc_attr($this->get_comment_or_reply_label(__('All %d %s displayed.', 'peepso-core'), $post->post_type, $comment_count));
            }
        }
    }

    /*
     * Display comment or reply label
     */
    public function get_comment_or_reply_label($text_to_display, $post_type, $count) {
        // Call _n() on the ENGLISH literals (not pre-translated variables) so make-pot extracts a
        // real singular/plural pair and complex-plural locales (pl/ru/ar/cs) can supply every form.
        // Passing already-translated strings into _n() made the lookup miss and collapse to the
        // English 1-vs-many rule on translated nouns (#8087 front-end review).
        if ($post_type === PeepSoActivityStream::CPT_COMMENT) {
            $noun = _n('reply', 'replies', $count, 'peepso-core');
        } else {
            $noun = _n('comment', 'comments', $count, 'peepso-core');
        }

        return sprintf($text_to_display, $count, $noun);
    }


    /*
     * outputs the contents of a single comment
     */
    public function show_comment()
    {
        PeepSoTemplate::exec_template('activity', 'comment', $this->comment_data);
    }

    /*
     * filter the SQL clauses; adding WHERE statements for comment ID
     * @param array $clauses array holding the clauses for the SQL being built
     * @param WP_Query $query Query instance
     * @return array The modified array of clauses
     */
    public function filter_post_clauses_comments($clauses, $query)
    {
        global $wpdb;

        if (!empty($query->query['_comment_object_id']) && !empty($query->query['_comment_module_id']))
            $clauses['where'] .= $wpdb->prepare(' AND `act`.`act_comment_object_id` = %d AND `act`.`act_comment_module_id` = %d', $query->query['_comment_object_id'], $query->query['_comment_module_id']);

        return ($clauses);
    }

    /**
     * Sets the JOIN for the activity table.
     * @param  string $join The JOIN clause
     * @return string
     */
    public function filter_act_join($join)
    {
        global $wpdb;

        $join .= " LEFT JOIN `{$wpdb->prefix}" . self::TABLE_NAME . "` `act` ON `act`.`act_external_id`=`{$wpdb->posts}`.`id` ";

        return ($join);
    }

    /*
     * filter the SQL clauses; adding our JOINs and other conditions
     * @param array $clauses array holding the clauses for the SQL being built
     * @param WP_Query $query Query instance
     * @return array The modified array of clauses
     */
    function filter_post_clauses($clauses, $query)
    {
        global $wpdb;

        // Add the default groupby clause anyway, to prevent duplicate records retrieved, one instance of this behavior is showing comments with the friends add-on enabled
        $clauses['groupby'] = "{$wpdb->posts}.`ID`";

        // determine if this is a "permalink" type request #57
        $is_permalink = PeepSoActivity::is_permalink_ajax();

        if (PeepSo::get_option_new('pinned_posts_enable') && !isset($query->query['_bypass_pinned'])) {
            if (isset($query->query['pinned']) && $query->query['pinned'] === TRUE) {
                $clauses['join'] .= " INNER JOIN (SELECT `post_id`, `meta_value` FROM `$wpdb->postmeta` WHERE `meta_key` = 'peepso_pinned') `mt1` ON " .
                    " `mt1`.`post_id`=`$wpdb->posts`.`ID` ";
                $clauses['orderby'] = " `mt1`.`meta_value` DESC ";

            } else {
                $clauses['where'] .= " AND `$wpdb->posts`.`ID` NOT IN (SELECT `post_id` FROM `$wpdb->postmeta` WHERE `meta_key` = 'peepso_pinned') ";
            }
        }

        if (isset($query->query['_exclude_pending'])) {
            $exclude_pending_where = "({$wpdb->posts}.post_status = 'publish' OR (`{$wpdb->posts}`.`post_status` = 'pending' AND EXISTS (
                SELECT 1
                FROM `$wpdb->postmeta` `peepso_reported`
                WHERE `peepso_reported`.`post_id` = `$wpdb->posts`.`ID`
                    AND `peepso_reported`.`meta_key` = 'peepso_reported'
            )))";

            $clauses['where'] = str_replace("({$wpdb->posts}.post_status = 'publish')", $exclude_pending_where, $clauses['where']);
        }

        // add our columns to the query
        if (strpos(',', $clauses['fields']) === FALSE) {
            // SELECT wp_posts.*, act.*, author_id, author_name
            $clauses['fields'] .= ", `act`.*, `$wpdb->posts`.`post_author` AS `author_id`";
        }

        $owner = apply_filters('peepso_user_profile_id', 0);

        if (isset($query->query['_bypass_permalink']) && TRUE === $query->query['_bypass_permalink']) {
            $is_permalink = FALSE;
            $owner = 0;
        }

        // add JOIN clauses
        // $clauses['join'] .= " LEFT JOIN `{$wpdb->users}` `auth` ON `auth`.`ID`=`{$wpdb->posts}`.`post_author` ";
        if ($this->user_id > 0 && PeepSo::get_option_new('user_blocking_enable')) {

            // and blocked / ignored users
            $clauses['join'] .= " LEFT JOIN `{$wpdb->prefix}" . self::BLOCK_TABLE_NAME  . "` `blk` ON " .
                " (`blk`.`blk_user_id` = `{$wpdb->posts}`.`post_author` AND `blk`.`blk_blocked_id`={$this->user_id}) " .
                " OR (`blk`.`blk_user_id` = {$this->user_id} AND `blk`.`blk_blocked_id`=`{$wpdb->posts}`.`post_author` ) ";

            // exclude blocked users
            $clauses['where'] .= ' AND `blk_blocked_id` IS NULL ';
        }

        // if it's a permalink request *and* the CPT is the post (not comments!), adjust the WHERE clause
        if ($is_permalink) {
//			$permalink = $wpdb->escape($permalink);
            # commented out in #2479 - it broke loading comments
            # $clauses['where'] = $wpdb->prepare(" AND `{$wpdb->posts}`.`ID`='%s' ", PeepSoActivity::is_permalink_ajax(TRUE));
        } else {
            // adjust the WHERE clause
            if (FALSE === strpos('hide_activity_id', $clauses['where'])) {
                if ($owner > 0)
                    $clauses['where'] = " AND `act_owner_id`='{$owner}' " . $clauses['where'];

                // add checks for post's access
                if (is_user_logged_in()) {
                    // PRIVATE and owner by current user id  - OR -
                    // MEMBERS and user is logged in - OR -
                    // PUBLIC

                    if (!PeepSo::is_admin()) {
                        $access = ' ((`act_access`=' . PeepSo::ACCESS_PRIVATE . ' AND `act_owner_id`=' . get_current_user_id() . ') OR ' .
                            ' (`act_access`=' . PeepSo::ACCESS_MEMBERS . ') OR (`act_access`<=' . PeepSo::ACCESS_PUBLIC . ') ';

                        // Hooked methods must wrap the string within a paranthesis
                        $access = apply_filters('peepso_activity_post_filter_access', $access);

                        $access .= ') ';
                    }
                } else {
                    // PUBLIC
                    $access = ' (`act_access`<=' . PeepSo::ACCESS_PUBLIC . ' ) ';
                }

                if (!empty($access)) {
                    $clauses['where'] .= " AND {$access}";
                }
            }
        }

        if (PeepSo::get_option_new('order_post_asc')) {
            $sort_order = 'ASC';
        } else {
            $sort_order = 'DESC';
        }

        // add ORDER BY clause
        $sort_key = (isset($_REQUEST['stream_filter_sort_by']) && is_string($_REQUEST['stream_filter_sort_by']))
            ? $_REQUEST['stream_filter_sort_by'] : '';

        // #8037 ranking sorts (Top/Hot × period): LEFT JOIN this activity's ranking row and order by
        // its score, with a most-recent-activity tie-breaker. order_by_sql() returns FALSE for any
        // non-ranking key, so `new` / `new_and_commented` / `pinned_only` behave exactly as before.
        $ranking_orderby = FALSE;
        if (!$this->is_comment_query && class_exists('PeepSoActivityRankingEngine')) {
            $ranking_orderby = PeepSoActivityRankingEngine::order_by_sql($sort_key, 'psrank', $wpdb->posts);
        }

        if ($ranking_orderby) {
            $clauses['join'] .= " LEFT JOIN `{$wpdb->prefix}" . PeepSoActivityRanking::TABLE . "` `psrank` ON `psrank`.`rank_act_id` = `act`.`act_id` ";
            $clauses['orderby'] = $ranking_orderby;
        } else if ('new_and_commented' == $sort_key) {
            # 6208 dont order by if it was from comments query
            if (!$this->is_comment_query) {
                $clauses['orderby'] = " `{$wpdb->posts}`.`post_modified_gmt` {$sort_order} ";
            }
        } else if (!isset($clauses['orderby'])) {
            $clauses['orderby'] = " `{$wpdb->posts}`.`post_date_gmt` {$sort_order} ";
        }

        // indicate if we are loading a sgream item or ie a post for edition
        // @todo we might not need that
        if($this->is_loading_stream) {
            $clauses['peepso_is_stream'] = 1;
        }

        if ( !PeepSo::is_dev_mode('experimental_fast_queries') ) {
            $search = $this->_input->value('search', NULL, FALSE); // SQL Safe. optional, string to search
            $search_mode = $this->_input->value('search_mode', 'exact', array('exact','any')); // optional, whether to use exact phrase or any word

            $search_qry = $where = [];
            if(NULL !== $search && FALSE === strpos($clauses['where'], PeepSoActivityStream::CPT_COMMENT )) {

                // handling multiple terms
                #todo: find appropriate ways to search any terms
                #@reference: https://codex.wordpress.org/Class_Reference/WP_Query

                if('any' == $search_mode) {
                    $search = explode(' ', $search);
                    foreach ($search as $key) {
                        $search_qry[] = " (`{$wpdb->posts}`.`post_content` LIKE '%$key%') ";
                    }
                } else {
                    $search_qry[] = " (`{$wpdb->posts}`.`post_content` LIKE '%$search%') ";
                }

                $where[] = "(". implode(' OR ', $search_qry) .")";

                $where = apply_filters('peepso_activity_search_clauses', $where);

                $clauses['where'] .= " AND (". implode(' OR ', $where) .")";
            }
        }

        $clauses = apply_filters('peepso_activity_post_clauses', $clauses, $this->user_id);

        // Special case - follower stream
        if (!isset(PeepSo::$globals['search_repost'])) {
            if (isset($_REQUEST['stream_id']) && 'core_following' === $_REQUEST['stream_id'] && FALSE === strpos($clauses['where'], PeepSoActivityStream::CPT_COMMENT )) {
                $additional_where = [];
                if(class_exists('PeepSoPagesPlugin')) {
                    $additional_where['pages'] = '`peepso_page_meta`.`meta_value` IS NULL';
                }

                if(class_exists('PeepSoGroupsPlugin')) {
                    $additional_where['groups'] = '`pm`.`meta_value` IS NULL';
                }

                $where_clauses = '';
                if (count($additional_where)) {
                    $where_clauses = " AND ( " . implode(' AND ', $additional_where) . " ) ";
                }


                $following =  array(
                    'core' => "({$wpdb->posts}.`post_author`=".get_current_user_id() ." {$where_clauses})"
                );

                if(isset($_REQUEST['stream_filter_show_my_posts']) && 'hide_mine' == $_REQUEST['stream_filter_show_my_posts'] ) {
                    $following = array();
                }

                $following = apply_filters('peepso_activity_post_clauses_follow', $following);

                #echo "<pre>";var_dump($following);echo "</pre>";

                $where_following = '';
                if (count($following)) {
                    $where_following = " OR ( " . implode(' OR ', $following) . " ) ";
                }

                $clauses['join'] .= " LEFT JOIN `{$wpdb->prefix}" . self::ACTIVITY_FOLLOWERS_TABLE_NAME . "` `activity_followers` ON " .
                    " `activity_followers`.`post_id` = `act`.`act_external_id`";

                $clauses['where'] .= " AND ((`activity_followers`.`user_id`={$this->user_id} AND `activity_followers`.`follow` = 1 AND `activity_followers`.`post_id` IS NOT NULL {$where_clauses} ) {$where_following}) ";
            } else if (isset($GLOBALS['peepso_group_only'])) {
                $following = apply_filters('peepso_activity_post_clauses_follow', []);
                $clauses['where'] .= ' AND ( ' . $following['groups'] . ') ';
            }
        }

        // Special case - saved post stream
        if(PeepSo::get_option_new('post_save_enable')) {
            if (isset($_REQUEST['stream_id']) && 'core_saved' === $_REQUEST['stream_id'] && FALSE === strpos($clauses['where'], PeepSoActivityStream::CPT_COMMENT)) {

                $clauses['join'] .= " LEFT JOIN `{$wpdb->prefix}" . self::SAVED_POSTS_TABLE_NAME . "` `saved_posts` ON " .
                    " `saved_posts`.`post_id` = `act`.`act_id`";

                $clauses['where'] .= " AND `saved_posts`.`user_id`={$this->user_id} AND `saved_posts`.`post_id` IS NOT NULL ";
            }
        }

        // Special case - unread post stream
        if ( PeepSo::is_dev_mode('show_unread_posts_and_comments') ) {
            if (isset($_REQUEST['stream_id']) && 'core_unread' === $_REQUEST['stream_id'] && FALSE === strpos($clauses['where'], PeepSoActivityStream::CPT_COMMENT)) {
                $clauses['join'] .= " LEFT JOIN `{$wpdb->prefix}" . PeepSoActivityRanking::TABLE_READ . "` `unread_posts` ON " .
                    " `unread_posts`.`act_id` = `act`.`act_id` AND `unread_posts`.`user_id`={$this->user_id}";

                $clauses['where'] .= " AND `unread_posts`.`act_id` IS NULL ";
            }
        }  

        return $clauses;

    }

    /**
     * Filters for posts newer than $this->last_post_id
     * @param  string $where The WP_Query where clause
     * @return string
     */
    public function filter_since_id($where = '')
    {
        global $wpdb;

        if (NULL !== $this->last_post_id)
            $where .= $wpdb->prepare(" AND `{$wpdb->posts}`.`ID` > %d ", $this->last_post_id);

        return ($where);
    }

    /**
     * Filters for posts older than $this->first_post_id
     * @param  string $where The WP_Query where clause
     * @return string
     */
    public function filter_before_id($where = '')
    {
        global $wpdb;

        if (NULL !== $this->first_post_id)
            $where .= $wpdb->prepare(" AND `{$wpdb->posts}`.`ID` < %d ", $this->first_post_id);

        return ($where);
    }

    /*
     * Get the owner of a specific post
     * @param int $post_id The id of the post
     * @return int The user id of the owner of that post
     */
    private function get_post_owner($post_id)
    {
        global $wpdb;
        $sql = "SELECT `act_owner_id` FROM `{$wpdb->prefix}" . self::TABLE_NAME . "` " .
            " WHERE `act_id`=%d LIMIT 1";
        $owner = intval($wpdb->get_var($wpdb->prepare($sql, $post_id)));
        return ($owner);
    }


    /**
     * Get an activity stream item's owner ID
     * @param  int $post_id The post ID.
     * @return int The WP user ID.
     */
    public function get_owner_id($post_id, $module_id = self::MODULE_ID)
    {
        global $wpdb;

        $sql = "SELECT `act_owner_id` FROM `{$wpdb->prefix}" . self::TABLE_NAME . "` " .
            ' WHERE `act_external_id`=%d AND `act_module_id` = %d';
        $owner = intval($wpdb->get_var($wpdb->prepare($sql, $post_id, $module_id)));

        return ($owner);
    }


    /**
     * Return the original author of a post.
     * @param  int $post_id The post ID to get the author from.
     * @return int User id of the author of the content
     */
    public function get_author_id($post_id)
    {
        global $wpdb;

        $sql = "SELECT `post_author` FROM `{$wpdb->posts}` " .
            ' WHERE `ID`=%d ';
        $owner = intval($wpdb->get_var($wpdb->prepare($sql, $post_id)));
        return apply_filters('peepso_activity_author_id', $owner, intval($post_id));
    }

    //
    // the following are the AJAX callbacks
    //

    public function like(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id');
        $user_id = $this->_input->int('uid');
        if ($user_id != get_current_user_id()) {
            $resp->error(__('Invalid User id', 'peepso-core'));
            return;
        }

        $activity = $this->get_activity($act_id);
        $module_id = $activity->act_module_id;

        $act_post = $this->get_activity_post($act_id);
        $post_id = $act_post->ID;
        $owner_id = $this->get_author_id($post_id);

        $fSuccess = FALSE;
        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_LIKE, $user_id)) {
            $like = PeepSoLike::get_instance();
            $add_like = (FALSE === $like->user_liked($activity->act_external_id, $module_id, $user_id));

            if ($add_like)
            {
                $res = $like->add_like($activity->act_external_id, $module_id, $user_id, 1, $act_id);
            }
            else
            {
                $res = $like->remove_like($activity->act_external_id, $module_id, $user_id, 1, $act_id);
            }
            if (FALSE !== $res)
                $fSuccess = TRUE;
        }

        $resp->success($fSuccess);
        if ($fSuccess) {
            $count = $like->get_like_count($activity->act_external_id, $module_id);
            $resp->set('count', $count);
            ob_start();
            $this->show_like_count($count, $act_id);
            $resp->set('count_html', ob_get_clean());

            ob_start();
            $like = $this->get_like_status($activity->act_external_id, $module_id);

            $acts = array(
                'like' => array(
                    'href' => '#like',
                    'label' => $like['label'],
                    'class' => 'ps-comment__action ps-comment__action--like actaction-like' . ( $like['liked'] ? ' liked' : '' ),
                    'icon' => $like['icon'],
                    'click' => 'return activity.action_like(this, ' . $activity->act_id . ');',
                    'count' => $like['count'],
                ),
            );

            $this->_display_post_actions($post_id, $acts);

            $resp->set('like_html', ob_get_clean());

            // send owner an email
            if ($user_id !== $owner_id && $add_like) {
                $user_owner = PeepSoUser::get_instance($owner_id);
                $user = PeepSoUser::get_instance($user_id);
                $orig_post = get_post($post_id);
                $post_title = $orig_post->post_title;
                $object_id = $post_id;
                $object_module_id = PeepSoActivity::MODULE_ID;
                $parent_post_id = $post_id;
                if(0 !== intval($act_post->act_comment_object_id)) {
                    $parent_post = get_post($act_post->act_comment_object_id);
                    $parent_post_act = $this->get_activity_data($parent_post->ID, $act_post->act_comment_module_id);
                    $object_id = $act_post->act_comment_object_id;
                    $object_module_id = $act_post->act_comment_module_id;
                    $parent_post_id = $parent_post->ID;

                    // reveal comment on single activity view which ids are defined in the url hash
                    // for example `#comment.00.11.22.33` will be translated as follow:
                    //   00 = post's act_id
                    //   22 = comment's post_id
                    //   11 = comment's act_id
                    //   33 = reply's act_id (optional, if you want to show reply)

                    // check is parent is comment, so it'll be nested comments
                    if($parent_post->post_type === PeepSoActivityStream::CPT_COMMENT) {
                        $parent_post_comment = get_post($parent_post_act->act_comment_object_id);
                        $parent_post_comment_act = $this->get_activity_data($parent_post_comment->ID, $parent_post_act->act_comment_module_id);
                        $object_id = $parent_post_act->act_comment_object_id;
                        $object_module_id = $parent_post_act->act_comment_module_id;
                        $parent_post_id = $parent_post_comment->ID;

                        $post_title = $parent_post_comment->post_title . '#comment.' . $parent_post_comment_act->act_id . '.' . $parent_post->ID . '.' . $parent_post_act->act_id . '.' . $activity->act_external_id;
                    } else {

                        $post_title = $parent_post->post_title . '#comment.' . $parent_post_act->act_id . '.' . $orig_post->ID . '.' . $act_id;
                    }
                }

                // check anonymous
                if(PeepSo::get_option_new('postbox_anon_enabled')) {
                    $anon_op = get_post_meta($parent_post_id, PeepSo3_anon::META_POST_ANON_OP, TRUE);
                    if(PeepSo3_Utilities_String::maybe_strlen($anon_op) && intval($anon_op) == $user_id) {
                        $user = PeepSoUser::get_instance(PeepSo3_anon::get_instance()->anon_id);
                    }
                }

                $post_type = get_post_type($post_id);
                $post_type_object = get_post_type_object($post_type);

//				PeepSoMailQueue::add($owner_id, $data, __('Someone liked your post', 'peepso-core'), 'likepost');
                $data = array(
                    'permalink' => PeepSo::get_page('activity_status', FALSE) . $post_title ,
                    'post_content' => $orig_post->post_content,
                );

                $data = array_merge($data, $user->get_template_fields('from'), $user_owner->get_template_fields('user'));

                $i18n = __('Someone liked your comment', 'peepso-core');
                $message = 'Someone liked your comment';
                $args = ['peepso-core'];

                PeepSoMailQueue::add_notification_new($owner_id, $data, $message, $args, 'like_comment', 'like_comment', PeepSoActivity::MODULE_ID);

                # 2499 notify if only user is following
                // handle for photo album
                $object_id = apply_filters('peepso_activity_user_comment_object_id', $object_id, $object_module_id);
                $act_notif = new PeepSo3_Activity_Notifications($object_id, $owner_id);
                if ($act_notif->is_following()) {
                    $note = new PeepSoNotifications();

                    $i18n = __('liked your comment', 'peepso-core');
                    $message = 'liked your comment';
                    $args = ['peepso-core'];

                    $note->add_notification_new($user_id, $owner_id, $message, $args, 'like_comment', $module_id, $post_id, $act_id);
                }
            }
        } else {
            $resp->error(__('Unable to process', 'peepso-core'));
        }
    }

    /*
     * Allows user to edit a post
     */
    public function editpost(PeepSoAjaxResponse $resp)
    {
        $post_id = $this->_input->int('postid');
        $user_id = $this->_input->int('uid');
        $owner_id = intval($this->get_author_id($post_id));

        $this->set_user_id($user_id);
        $this->set_owner_id($user_id);
        $wpq = $this->get_post($post_id, $user_id);
        $this->next_post();

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            global $post;
            $data = array('cont' => $post->post_excerpt, 'post_id' => $post_id, 'act_id' => $post->act_id, 'module_id' => $post->act_module_id);
            $data = apply_filters('peepso_activity_post_edit', $data);
            if (isset($data['cont'])) {
                $data['cont'] = html_entity_decode($data['cont']);
            }
            $html = PeepSoTemplate::exec_template('activity', 'post-edit', $data, TRUE);

            $post_time_user_offset = NULL;
            if ($post->post_status == 'future') {
                // config
                $post_date = get_post_time('U', TRUE, $post_id);

                // post time & time adjusted to user's timezone
                $post_timestamp_user_offset = $post_date + 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());
                $post_time_user_offset = gmdate('Y-m-d H:i:s', $post_timestamp_user_offset);
            }

            $resp->set('html', $html);
            $resp->set('content', $data['cont']);
            $resp->set('act_id', $post->act_id);
            $resp->set('data', $data);
            $resp->set('future', $post_time_user_offset);
            $resp->success(1);
        } else {
            $resp->success(0);
            $resp->error(__('You do not have permission to edit this post.', 'peepso-core'));
        }
    }

    /*
     * Allows user to edit a post
     * @param PeepSoAjaxResponse $resp The response object
     */
    public function edit_description(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id', NULL);
        $user_id = $this->_input->int('uid');
        $type = $this->_input->value('type', NULL, FALSE); // SQL Safe
        $act_external_id = $this->_input->int('object_id', NULL);

        if (NULL === $act_id) {
            $resp->success(FALSE);
            $resp->error(__('Post not found.', 'peepso-core'));
            return;
        }

        $activity = $this->get_activity($act_id);
        $owner_id = intval($activity->act_owner_id);

        $this->set_user_id($user_id);
        $this->set_owner_id($user_id);

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            $cont = $activity->act_description;
            $objects = apply_filters('peepso_get_object_' . $type, array(), $act_external_id);

            // object caption should be the post content in case of single object
            if ( 1 === count($objects)) {
                $object = array_pop($objects);
                if(!isset($object['using_activity_desc'])) {
                    $cont = $object['post']->post_excerpt;
                }
            }

            $data = array('cont' => $cont, 'act_id' => $act_id, 'type' => $type, 'act_external_id' => $act_external_id);
            $html = PeepSoTemplate::exec_template('activity', 'description-edit', $data, TRUE);

            $resp->set('html', $html);
            $resp->set('act_id', $activity->act_id);
            $resp->success(1);
        } else {
            $resp->success(0);
            $resp->error(__('You do not have permission to edit this post.', 'peepso-core'));
        }
    }

    /**
     * AJAX callback
     * Saves the description by using a custom query that allows NULL values
     * @param PeepSoAjaxResponse $resp The response object
     */
    public function save_description(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id', NULL);
        $description = $this->_input->value('description', NULL, FALSE); // SQL Safe.
        $user_id = $this->_input->int('uid');
        $type = $this->_input->value('type', NULL, FALSE); // SQL Safe
        $act_external_id = $this->_input->int('object_id', NULL);
        $success = FALSE;

        if (NULL === $act_id) {
            $resp->success(FALSE);
            $resp->error(__('Post not found.', 'peepso-core'));
            return;
        }

        $activity = $this->get_activity($act_id);
        $owner_id = intval($activity->act_owner_id);

        $this->set_user_id($user_id);
        $this->set_owner_id($user_id);

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            $objects = apply_filters('peepso_get_object_' . $type, array(), $act_external_id);

            // object caption should be the post content in case of single object
            $using_activity_desc = FALSE;
            if (1 === count($objects)) {
                $object = array_pop($objects);
                if(!isset($object['using_activity_desc'])) {
                    $using_activity_desc = TRUE;
                    $post_id = $object['post']->ID;

                    $description = substr(PeepSoSecurity::strip_content($description), 0, PeepSo::get_option('site_status_limit', 4000));
                    add_filter('oembed_result', array(&$this, 'oembed_result'), 10, 3);
                    $filtered_content = apply_filters('peepso_activity_post_content', $description, $post_id);
                    remove_filter('oembed_result', array(&$this, 'oembed_result'));

                    wp_update_post(array(
                        'ID' => $post_id,
                        'post_content' => $filtered_content,
                        'post_excerpt' => $description
                    ), true);

                    if (is_wp_error($post_id)) {
                        $resp->error(__('Could not update the description.', 'peepso-core'));
                    } else {
                        $success = TRUE;

                        $activity = PeepSoActivity::get_instance();
                        $object['post']->post_content = $filtered_content;
                        $description = $activity->content($object['post'], FALSE);
                        $resp->set('html', $description);
                    }
                }
            }

            if (!$using_activity_desc) {

                global $wpdb;

                // Use custom query to update row, accepts NULL
                $query = "UPDATE `{$wpdb->prefix}" . self::TABLE_NAME . "` SET `act_description`=%s";

                if (empty($description))
                    $query = sprintf($query, 'NULL');
                else
                    $query = $wpdb->prepare($query, $description);

                $query .= $wpdb->prepare(' WHERE `act_id`=%d', $act_id);

                $success = $wpdb->query($query);
                $resp->success($success);

                // PeepSo/peepso#2588 do not execute shortcode on peepso activity or comment
                // $description = do_shortcode($description);

                $description = substr(PeepSoSecurity::strip_content($description), 0, PeepSo::get_option('site_status_limit', 4000));

                $psnewline = 'PEEPSONEWLINE';
                if(defined('PEEPSO_NEWLINE')) {
                    $psnewline = PEEPSO_NEWLINE;
                }

                $description = strip_tags($description, '<a>');
                $description = str_replace("\n",$psnewline, $description);
                $description = convert_smilies($description);
                $description = str_replace($psnewline, "<br/>", $description);

                $description = $description;
            }

            $resp->success($success);
            if (FALSE === $success)
                $resp->error(__('Could not update the description.', 'peepso-core'));
            else
                $resp->set('html', $description);
        } else {
            $resp->success(0);
            $resp->error(__('You do not have permission to edit this post.', 'peepso-core'));
        }
    }

    /*
     * Saves a post after editing
     * @param PeepSoAjaxResponse $resp The response object
     */
    public function savepost(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id');
        $act_post = $this->get_activity_post($act_id);
        $post_id = $act_post->ID;
        $owner_id = $this->get_author_id($post_id);
        $user_id = $this->_input->int('uid');
        $post_content = $this->_input->value('post', '', FALSE); // SQL Safe.
        $future = $this->_input->value('future', NULL, FALSE); // SQL Safe

        // global $post is used by other plugins when checking permissions
        global $post;
        $post = get_post($post_id);
        // don't do anything if contents are empty
        if (empty($post_content) && !apply_filters('peepso_activity_allow_empty_content', FALSE)) {
            $resp->success(FALSE);
            $resp->error(__('Post is empty', 'peepso-core'));
        } else if (PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            // PeepSo/PeepSo#8034 Preserve the author's "Remove preview" choice across edits.
            // Previously the '_peepso_display_link_preview' flag was deleted on every edit, so a
            // post that still contained a link had its preview rebuilt and shown again each time
            // it was edited, forcing the author to remove it over and over. Leaving the flag in
            // place lets content_attach_media() keep honoring a prior removal (flag === '0').

            $post_content = substr(PeepSoSecurity::strip_content($post_content), 0, PeepSo::get_option('site_status_limit', 4000));
            add_filter('oembed_result', array(&$this, 'oembed_result'), 10, 3);
            $filtered_content = apply_filters('peepso_activity_post_content', $post_content, $post_id);
            remove_filter('oembed_result', array(&$this, 'oembed_result'));

            $data = apply_filters('peepso_pre_write_content', array(
                'post_content' => $filtered_content,
                'post_excerpt' => $post_content
            ), self::MODULE_ID, __FUNCTION__);

            // update date future post
            if($future != NULL && !empty($future)) {

                if(is_numeric($future)) {
                    $future = $future / 1000;
                } else {
                    $future = strtotime($future);
                }

                // Attempt to fix timezone offset
                $post_date = $future - 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());
                $data['post_status'] = 'future';
                $data['post_date'] = date('Y-m-d H:i:s', $post_date);
                $data['post_date_gmt'] = gmdate('Y-m-d H:i:s', $post_date);

                $config_date_format = get_option('date_format'). ' ' . get_option('time_format');
                $timestamp = sprintf(__('Scheduled for %s','peepso-core'), date_i18n($config_date_format, $future));

                $resp->set('timestamp', $timestamp);
            }

            // if post as immediately and recent status is future
            if($future == NULL && $act_post->post_status == 'future') {
                $data['post_status'] = 'publish';
                $data['post_date'] = current_time('mysql');
                $data['post_date_gmt'] = current_time('mysql', TRUE);

                $curr_date = current_time('timestamp', TRUE);
                $timestamp = PeepSoTemplate::time_elapsed(mysql2date('U', $data['post_date_gmt'], FALSE), $curr_date);

                $resp->set('timestamp', $timestamp);
            }

            PeepSoActivity::store_revision($post_id, $filtered_content);

            global $wpdb;
            $edit = $wpdb->update($wpdb->posts, $data, array('ID' => $post_id));
            $_post = $this->get_activity_data($post_id);

            if (empty($edit)) {
                $data['post_content'] = wp_encode_emoji($data['post_content']);
                $data['post_excerpt'] = wp_encode_emoji($data['post_excerpt']);
                $edit = $wpdb->update($wpdb->posts, $data, array('ID' => $post_id));
            }

            if (empty($_post->act_repost_id)) {
                $this->handle_embed_data();

                $this->save_peepso_media($post_id);

                $refresh = PeepSo::get_option('refresh_embeds');
                if($refresh > 0) {
                    PeepSo3_Mayfly::set('peepso_cache_media_' . $post_id, 1, $refresh);
                }
            }
            do_action('peepso_activity_after_save_post', $post_id);

            $note = new PeepSoNotifications();
            $users = $this->get_comment_users($post_id, $act_post->act_module_id);

            $i18n = __('updated a post', 'peepso-core');
            $message = 'updated a post';
            $args = ['peepso-core'];

            // notify post owner

	        // If this is an anonymous post, send the notifications "from" Anonymous User
	        $actor_id = $user_id;
	        $anon_op = get_post_meta($post_id, PeepSo3_anon::META_POST_ANON_OP, TRUE);
	        if(PeepSo3_Utilities_String::maybe_strlen($anon_op) > 0) {
		        $actor_id = PeepSo3_Anon::get_instance()->anon_id;
	        }
            if ($owner_id !== $user_id) {
                $note->add_notification_new($user_id, $owner_id, $message, $args, 'wall_post', self::MODULE_ID, $post_id, $act_id);
            }

            $skip = array($owner_id, $user_id);
            while ($users->have_posts()) {
                $users->next_post();
                $skip[] = $users->post->post_author;

                # 2499 notify if only user is following
                $act_notif = new PeepSo3_Activity_Notifications($post_id, $users->post->post_author);
                if (intval($users->post->post_author) !== $owner_id && intval($users->post->post_author) !== $user_id) {
                    $note->add_notification_new($actor_id, $users->post->post_author, $message, $args, 'wall_post', self::MODULE_ID, $post_id, $act_id);
                }
            }

            // #2499 send notification for post followers
            $act_notif = new PeepSo3_Activity_Notifications($post_id, $user_id);
            $followers = $act_notif->get_followers();
            foreach ($followers as $follower) {
                if (!in_array($follower, $skip)) {
                    $act_notif = new PeepSo3_Activity_Notifications($post_id, $follower);
                    if ($act_notif->is_following()) {
                        $note->add_notification_new($actor_id, $follower, $message, $args, 'wall_post', self::MODULE_ID, $post_id, $act_id);
                    }
                }
            }

            $this->get_post($post_id, $owner_id, 0);
            $this->next_post();

            $html = $this->content(NULL, FALSE);

            ob_start();
            $this->post_attachment();
            $resp->success(1);
            $resp->set('html', $html);
            $resp->set('attachments', ob_get_clean());

            ob_start();
            $this->post_actions();
            $resp->set('actions', ob_get_clean());

            $post_extras = apply_filters('peepso_post_extras', array());
            $post_extras = implode(' ', $post_extras);
            $resp->set('extras', $post_extras);
        }
    }

    /*
     * Add a user to user's list of blocked users
     * @param PeepSoAjaxResponse $resp The AJAX Response instance
     */
    public function blockuser(PeepSoAjaxResponse $resp)
    {
        $user_id = $this->_input->int('uid');
        $block_id = $this->_input->int('user_id');

        // don't allow users to block themselves
        if ($user_id === $block_id) {
            $resp->success(0);
            return;
        }

        // don't allow users to block admin
        $user = PeepSoUser::get_instance($block_id);
        $role = $user->get_user_role();
        $wprole = $user->get_role();
        if ($role === 'admin' || in_array('administrator', $wprole)) {
            $resp->success(0);
            $resp->error(__('You can\'t block admin account.', 'peepso-core'));
            return;
        }

        // enforce the maximum-blocks limit (#4553)
        $block = new PeepSoBlockUsers();
        $limit = intval(PeepSo::get_option('user_blocking_max_amount', 50));
        if ($limit > 0 && $block->get_count_for_user($user_id) >= $limit) {
            $resp->success(0);
            $resp->error(sprintf(__('You have reached the maximum number of blocked users (%d).', 'peepso-core'), $limit));
            return;
        }

        if (PeepSo::check_permissions($user_id, PeepSo::PERM_POST, $user_id)) {
            $block->block_user_from_user($block_id, $user_id);

            $blocked_member_url = PeepSo::get_page('members');
            if(0 == PeepSo::get_option('disable_questionmark_urls', 0)) {
                $blocked_member_url .= '?';
            }
            $blocked_member_url .= 'blocked/';

            $resp->success(1);
            $resp->set('redirect', $blocked_member_url);
        }
    }


    /*
     * Remove a user from user's list of blocked users
     * @param PeepSoAjaxResponse $resp The AJAX Response instance
     */
    public function unblockuser(PeepSoAjaxResponse $resp)
    {
        $user_id = $this->_input->int('uid');
        $block_id = $this->_input->int('user_id');

        if (PeepSo::check_permissions($user_id, PeepSo::PERM_POST, $user_id)) {
            $block = new PeepSoBlockUsers();
            $block->delete_by_id(array($block_id), $user_id);
            $resp->success(1);
        }
    }

    public function set_ban_status(PeepSoAjaxResponse $resp)
    {
        // check admin access
        if( FALSE == PeePso::is_admin()) {
            $resp->success(0);
            $resp->error(__('You do not have permission to do that.', 'peepso-core'));
            return;
        }
        $user_id = $this->_input->int('user_id');
        $ban_status = $this->_input->int('ban_status', 1);

        // don't allow users to un/ban themselves
        if ( get_current_user_id() === $user_id ) {
            $resp->success(0);
            $resp->error(__('You cannot ban yourself', 'peepso-core'));
            return;
        }

        $user = PeepSoUser::get_instance($user_id);

        if( $user_id == 0 || NULL == $user->get_id() ) {
            $resp->success(0);
            $resp->error(__('Invalid user', 'peepso-core'));
            return;
        }

        // Don't allow banning administrators
        if(PeepSo::is_admin($user_id)) {
            $resp->success(0);
            $resp->error(__('You cannot ban administrators', 'peepso-core'));
            return;
        }


        $resp->set('header', __('Notice', 'peepso-core'));

        if( 0 == $ban_status ) {
            $user->set_user_role('member');
            $resp->set('message', sprintf(__('%s has been unbanned', 'peepso-core'), trim(strip_tags($user->get_fullname()))));
            $resp->success(1);
        } else {
            $ban_type = $this->_input->value('ban_type', 'ban_forever', array('ban_period', 'ban_forever')); // SQL Safe

            if($ban_type == 'ban_period') {
                $ban_period_date = $this->_input->value('ban_period_date', '', FALSE); // SQL Safe
                new PeepSoError($ban_period_date);
                if(!empty($ban_period_date)) {
                    #$ban_period_date = explode('-', $ban_period_date);
                    #$ban_period_date = implode('/', $ban_period_date);
                    $ban_period_date = strtotime($ban_period_date . ' 23:59:59');

                    /*$gmt_offset = get_option('gmt_offset');
                    if (strpos('-', $gmt_offset) === FALSE) {
                        $gmt_offset = '-' . $gmt_offset;
                    } else {
                        $gmt_offset = '+' . abs($gmt_offset);
                    }

                    $schedule_time = strtotime($gmt_offset . ' hours', $ban_period_date);*/

                    $user->ban($ban_period_date);
                    $resp->set('message', sprintf(
                        __('%s has been banned until %s', 'peepso-core'),
                        trim(strip_tags($user->get_fullname())),
                        date_i18n(get_option('date_format'), $ban_period_date)
                    ));
                    $resp->success(1);
                }
                else
                {
                    $resp->success(0);
                    $resp->error(__('Missing ban period date', 'peepso-core'));
                    return;
                }

            } else {
                $user->ban(0);
                $resp->set('message', sprintf(__('%s has been banned', 'peepso-core'), trim(strip_tags($user->get_fullname()))));
                $resp->success(1);
            }
        }
    }


    /*
     * Called from AJAX handler to delete a post/comment
     * @param AjaxResponse $resp The AJAX response
     */
    public function delete(PeepSoAjaxResponse $resp)
    {
        $post_id = $this->_input->int('postid');
        $user_id = $this->_input->int('uid');

        if (!wp_verify_nonce($this->_input->value('_wpnonce','',FALSE), 'peepso-nonce')) {
            $resp->error(__('You do not have permission to delete this post.', 'peepso-core'));
            $resp->success(FALSE);
            return;
        }

        $args = array(
            'p' => $post_id,
            'post_type' => array(PeepSoActivityStream::CPT_POST, PeepSoActivityStream::CPT_COMMENT),
            'post_status' => array('publish', 'pending', 'future'),
            '_bypass_pinned' => TRUE
        );

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        $post_query = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);

        // global $post is used by other plugins when checking permissions
        global $post;
        $post = $post_query->post;
        // verify it's the current user AND they have ownership of the item
        if (PeepSo::check_permissions(intval($post->author_id), PeepSo::PERM_POST_DELETE, $user_id) ||
            PeepSo::check_permissions(intval($post->act_owner_id), PeepSo::PERM_POST_DELETE, $user_id)) {

            // if is a comment
            $rank = new PeepSoActivityRanking();

            if ($post->post_type === PeepSoActivityStream::CPT_COMMENT)
            {
                $act_data = $this->get_activity_data($post->act_comment_object_id, $post->act_comment_module_id);
                if(isset($act_data->act_id)) {
                    $rank->mark_dirty($act_data->act_id);
                    $this->recalculate_last_update($post_id, $act_data);
                }
            }

            if (intval($post->act_repost_id) !== 0)
            {
                $orig_post = $this->get_activity_post($post->act_repost_id);
                $rank->mark_dirty($orig_post->act_id);
            }

            $this->delete_post($post_id);
            $resp->set('act_id', $post->act_id);
            $resp->success(TRUE);
        } else {
            $resp->success(FALSE);
            $resp->error(__('You do not have permission to do that.', 'peepso-core'));
        }
    }

    /*
     * Called from AJAX handler to remove link preview on a post/comment
     * @param AjaxResponse $resp The AJAX response
     */
    public function remove_link_preview(PeepSoAjaxResponse $resp)
    {
        $post_id = $this->_input->int('postid');
        $user_id = $this->_input->int('uid');

        $args = array(
            'p' => $post_id,
            'post_type' => array(PeepSoActivityStream::CPT_POST, PeepSoActivityStream::CPT_COMMENT)
        );

        // perform the query, with a filter to add the peepso_activity table
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        $post_query = new WP_Query($args);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);

        $post = $post_query->post;
        // verify it's the current user AND they have ownership of the item
        if (PeepSo::check_permissions(intval($post->author_id), PeepSo::PERM_POST_DELETE, $user_id) ||
            PeepSo::check_permissions(intval($post->act_owner_id), PeepSo::PERM_POST_DELETE, $user_id)) {


            // PeepSo/PeepSo#8034 Handle posts and comments the same way. Record the
            // author's "Remove preview" choice via the '_peepso_display_link_preview'
            // flag (honored by content_attach_media()) so it survives edits for
            // comments too, not only posts. Previously the comment branch cleared
            // peepso_media but not the flag, so editing the comment rebuilt the
            // preview from the still-present link and forced the author to remove it
            // again.
            update_post_meta($post_id, '_peepso_display_link_preview', 0);
            delete_post_meta($post_id, 'peepso_media');

            $resp->set('act_id', $post->act_id);
            $resp->success(TRUE);
        } else {
            $resp->success(FALSE);
            $resp->error(__('You do not have permission to do that.', 'peepso-core'));
        }
    }

    /**
     * `peepso_activity_delete` callback
     * Deletes a post or comment based on activity
     * @param  array $activity
     */
    public function delete_post_or_comment($activity)
    {
        if (self::MODULE_ID === intval($activity->act_module_id))
            if (PeepSo::check_permissions($this->get_author_id($activity->act_external_id), PeepSo::PERM_POST_DELETE, get_current_user_id()))
                $this->delete_post($activity->act_external_id);
    }

    /**
     * Calls delete_activity via ajax
     * @param PeepSoAjaxResponse $resp The AJAX Response instance
     */
    public function ajax_delete_activity(PeepSoAjaxResponse $resp)
    {
        // SQL safe, WP sanitizes it
        if (wp_verify_nonce($this->_input->value('_wpnonce','',FALSE), 'activity-delete' )) {
            $act_id = $this->_input->int('act_id', NULL);
            $activity = $this->get_activity($act_id);

            // Allows other addons to send additional data.
            do_action('peepso_ajax_before_delete_activity', $resp, $activity);

            $delete = $this->delete_activity($act_id);

            do_action('peepso_ajax_after_delete_activity', $resp, $activity, $delete);

            if (is_wp_error($delete)) {
                $resp->success(FALSE);
                $resp->error($delete->get_error_message());
            } else {
                $resp->set('module_id', $activity->act_module_id);
                $resp->success(TRUE);
            }
        } else {
            $resp->success(FALSE);
            $resp->error(__('Could not verify nonce.', 'peepso-core'));
        }
    }

    /**
     * Deletes an activity and calls the `peepso_activity_delete` action
     * @param  int $act_id The activity to delete
     * @return bolean
     */
    public function delete_activity($act_id)
    {
        $activity = $this->get_activity($act_id);

        if (FALSE === PeepSo::check_permissions(intval($activity->act_owner_id), PeepSo::PERM_POST_DELETE, get_current_user_id()))
            return (new WP_Error('no_access', __('You do not have permission to do that.', 'peepso-core')));
        else {
            global $wpdb;

            do_action('peepso_activity_delete', $activity);
            $wpdb->delete($wpdb->prefix . self::TABLE_NAME, array('act_id' => $act_id));

            return (TRUE);
        }
    }

    /*
     * Report a post as inappropriate content
     * @param PeepSoAjaxResponse $resp The AJAX response object
     */
    public function report(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id');
        $act_data = $this->get_activity($act_id);
        $post_id = $act_data->act_external_id;
        $user_id = $this->_input->int('uid');
        $orig_post = get_post($post_id);

        if (PeepSo::check_permissions($this->get_post_owner($act_id), PeepSo::PERM_REPORT, $user_id)) {
            $reasons = str_replace("\r", '', PeepSo::get_option('site_reporting_types', __('Spam', 'peepso-core')));
            $reasons = explode("\n", $reasons);
            $reasons = apply_filters('peepso_activity_report_reasons', $reasons);

            $reason = $this->_input->value('reason', __('Other','peepso-core'), $reasons); // SQL Safe
            $reason_desc = $this->_input->value('reason_desc', '', FALSE); // SQL Safe
            $rep = new PeepSoReport();

            if (!$rep->is_reported_by_user($post_id, $user_id, $act_data->act_module_id)) {


                if(!strlen(trim($reason_desc))) {
                    $reason_desc = 'No extra information provided';
                }

                $rep->add_report($post_id, $user_id, $act_data->act_module_id, $reason, $reason_desc);

                // notify to admin
                if (PeepSo::get_option('reporting_notify_email', 0)) {

                    $reason_email =  "$reason - $reason_desc";
                    $wpuser = PeepSoUser::get_instance($user_id);

                    $permalink = PeepSo::get_page('activity_status') . $orig_post->post_title;

                    if (intval($act_data->act_comment_object_id) !== 0) {
                        $comment_activity = $this->get_activity_data($act_data->act_comment_object_id, $act_data->act_comment_module_id);
                        if (intval($comment_activity->act_comment_object_id) !== 0) {
                            $post_activity = $this->get_activity_data($comment_activity->act_comment_object_id, $comment_activity->act_comment_module_id);

                            $parent_comment = $this->get_activity_post($comment_activity->act_id);
                            $parent_post = $this->get_activity_post($post_activity->act_id);
                            $parent_id = $parent_comment->act_external_id;

                            $post_link = PeepSo::get_page('activity_status') . $parent_post->post_title . '/';
                            $permalink = $post_link . '?t=' . time() . '#comment.' . $post_activity->act_id . '.' . $parent_comment->ID . '.' . $comment_activity->act_id . '.' . $act_data->act_external_id;
                        } else {
                            $post_activity = $comment_activity;

                            $parent_post = $this->get_activity_post($post_activity->act_id);
                            $permalink = PeepSo::get_page('activity_status') .  $parent_post->post_title . '/#comment.' . $post_activity->act_id . '.' . $post_id . '.' . $act_data->act_external_id;
                        }
                    }

                    $data = array(
                        'userlogin' => $wpuser->get_username(),
                        'userfullname' => trim(strip_tags($wpuser->get_fullname())),
                        'userfirstname' => $wpuser->get_firstname(),
                        'permalink' => PeepSo::get_page('activity').'#reported',
                        'activityurl' => $permalink,
                        'reason' => $reason_email,
                    );

                    $admins = $rep->admin_emails();


                    if (count($admins) > 0) {
                        foreach ($admins as $admin) {
                            $data['useremail'] = $admin['email'];
                            $data['currentuserfullname'] = $admin['name'];
                            $admin_id = $admin['id'];
                            PeepSoMailQueue::add_message($admin_id, $data, __('{sitename} - Reported Content', 'peepso-core'), 'reported_content', 'reported_content', 0,1);
                        }
                    }
                }

                $resp->success(TRUE);
                $resp->notice(__('Report sent. Thank you!', 'peepso-core'));
            } else {
                $resp->success(FALSE);
                $resp->notice(__('You already reported this', 'peepso-core'));
            }


        } else {
            $resp->success(FALSE);
            $resp->error(__('You do not have permission to do that.', 'peepso-core'));
        }
    }

    public function ignore_reports(PeepSoAjaxResponse $resp) {
        if(PeepSo::is_admin()) {
            $rep = new PeepSoReport();
            $resp->success($rep->hide_reports($this->_input->int('post_id',0), $this->_input->int('module_id',0)));
        }
    }

    /*
     * Writes a comment on a post
     * @param PeepSoAjaxResponse $resp The AJAX response object
     */
    public function makecomment(PeepSoAjaxResponse $resp)
    {
        $content = $this->_input->raw('content');

        // don't allow empty comments
        if (empty($content) && !apply_filters('peepso_activity_allow_empty_comment', FALSE)) {
            $resp->success(FALSE);
            $resp->notice(__('Comment is empty', 'peepso-core'));
            return;
        }

        $act_id = $this->_input->int('act_id');
        $activity = $this->get_activity($act_id);

        if (NULL === $activity) {
            $resp->success(FALSE);
            $resp->notice(__('Activity not found', 'peepso-core'));
            return;
        }

        $module_id = $activity->act_module_id;
        $user_id = $this->_input->int('uid');
        $owner_id = $activity->act_owner_id;
        $post_id = $activity->act_external_id;

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_COMMENT, $user_id)) {
            $args = array(
                'content' => $content,
                'user_id' => $user_id,
                'target_user_id' => $owner_id,
//				'type' => $type,
                'written' => 0,
            );

            $extra = array('module_id' => $module_id);

            $id = $this->add_comment($post_id, $user_id, $content, $extra);
            $resp->set('has_max_comments', FALSE);

            if (FALSE !== $id) {
                $args['written'] = 1;
                $args['post_id'] = $id;
            }

            if (isset($args['written']) && 1 === $args['written']) {
                $resp->success(TRUE);

                $this->set_user_id($user_id);
                $this->set_owner_id($owner_id);

                $this->last_post_id = $this->_input->int('last', NULL);
                add_filter('posts_where', array(&$this, 'filter_since_id'));
                $wpq = $this->get_comments($post_id, NULL, 1, NULL, $module_id);
                remove_filter('posts_where' , array(&$this, 'filter_since_id'));

                if ($this->has_comments()) { // ($wpq->have_posts()) {
                    ob_start();
                    while ($this->next_comment())
                        $this->show_comment();

                    $comment_data = ob_get_clean();

                    $resp->set('html', $comment_data);
                }
            } else {
                $resp->success(FALSE);
                if ($max_comments)
                    $resp->error(__('The comment limit for this post has been reached.', 'peepso-core'));
                else
                    $resp->error(__('Error in writing Activity Stream comment.', 'peepso-core'));
            }
        } else {
            $resp->success(FALSE);
            $resp->error(__('You don\'t have permissions for that ', 'peepso-core') . $user_id . '/' . $owner_id);
        }
    }

    //
    // the following are the Activity Stream template tag methods
    //

    /*
     * Output post action options for the post
     */
    public function comment_actions()
    {
        global $post;

        $logged_in = is_user_logged_in();		// we're using this a lot, save function overhead

        $like = $this->get_like_status($post->act_external_id, $post->act_module_id);
        $acts = array();
        if (apply_filters('peepso_permissions_reactions_create', TRUE)) {
            $acts['like'] = array(
                'href' => '#like',
                'label' => $like['label'],
                'class' => 'ps-comment__action--like actaction-like' . ( $like['liked'] ? ' liked' : '' ),
                'icon' => $like['icon'],
                'click' => 'activity.comment_action_like(this, ' . $post->act_id . '); return false;',
                'count' => $like['count'],
            );
        }

        // save comment query
        $comment_query = $this->comment_query;

        $comments_var = $this->get_comments_var($post);
        $comments_batch = $comments_var['comments_batch'];
        $this->get_comments($post->act_external_id, NULL, 1, $comments_batch, $post->act_module_id);
        $this->comment_query->posts = array_reverse($this->comment_query->posts);

        // if (count($this->comment_query->posts) > 0) {
        //     $reply_label = __('View Replies', 'peepso-core');
        //     $reply_icon = 'eye';
        // } else {
        //     $reply_label = __('Reply', 'peepso-core');
        //     $reply_icon = 'plus';
        // }
        $show_replybutton = apply_filters('peepso_commentsbox_display', apply_filters('peepso_permissions_comment_create', TRUE), $post->ID);

        $disable_comment = FALSE;

        $root_post = $post;

        if ($root_post->post_type == PeepSoActivityStream::CPT_COMMENT) {
            $root_post = $this->get_comment($post->ID)->post;
            $act = $this->get_activity_data($root_post->act_comment_object_id, $root_post->act_comment_module_id);
            $root_post = $this->get_activity_post($act->act_id);

            // if still a comment
            if ($root_post->post_type == PeepSoActivityStream::CPT_COMMENT) {
                $act = $this->get_activity_data($root_post->act_comment_object_id, $root_post->act_comment_module_id);
                $root_post = $this->get_activity_post($act->act_id);
            }
        }

        if (!empty(get_post_meta($root_post->ID, 'peepso_disable_comments', TRUE))) {
            $disable_comment = TRUE;
        }

        if ($show_replybutton) {
            $reply_label = __('Reply', 'peepso-core');
            $reply_icon = 'gcir gci-comment-dots';

            $author_data = '{}';
            if ( 1 == PeepSo::get_option_new('mentions_auto_on_comment_reply') ) {
                $author = PeepSoUser::get_instance($post->post_author);
                $author_data = '{ id: ' . $post->post_author . ', name: \'' . esc_js( $author->get_fullname() ) . '\' }';

                if ( 1 == PeepSo::get_option_new('postbox_anon_enabled') ) {
                    $anon_op = get_post_meta($post->ID, PeepSo3_anon::META_POST_ANON_OP, TRUE);
                    if (PeepSo3_Utilities_String::maybe_strlen($anon_op)) {
                        $anon_id = PeepSo3_Anon::get_instance()->anon_id;
                        $anon_name = PeepSoUser::get_instance($anon_id)->get_fullname();
                        $author_data = '{ id: ' . $anon_id . ', name: \'' . esc_js( $anon_name ) . '\' }';
                    }
                }
            }

            $author_data = apply_filters('peepso_comment_reply_author_data', $author_data, $post);

            $acts['reply'] = array(
                'href' => '#reply',
                'label' => $reply_label,
                'class' => 'ps-comment__action--reply actaction-reply ps-js-btn-reply',
                'icon' => $reply_icon,
                'click' => 'activity.comment_action_reply(' . $post->act_id . ', ' . $post->ID . ', this, ' . $author_data . '); return false;',
                'extra' => $disable_comment ? ' style="display:none"' : '',
            );
        }

        // restore comment query
        $this->comment_query = $comment_query;

        if (! PeepSo::check_permissions($post->post_author, PeepSo::PERM_POST_LIKE, get_current_user_id()))
            unset($acts['like'], $acts['reply']);

        $acts = apply_filters('peepso_activity_comment_actions', $acts);

        // if no actions, exit
        if (0 === count($acts))
            return;

        foreach ($acts as $name => $act) {
            if ($name == 'like' && !apply_filters('peepso_permissions_reactions_create', TRUE)) {
                continue;
            }

            $stream_id = isset($act['data-stream-id']) ? $act['data-stream-id'] : $post->ID;
            echo '<a data-stream-id="', esc_attr($stream_id), '" ';

            $act_data = array_filter($act, function($k) { return 0 === strpos($k, 'data-') && 'data-stream-id' !== $k; }, ARRAY_FILTER_USE_KEY);
            foreach ($act_data as $key => $value) {
                echo ' ', $key, '="', esc_attr($value), '"';
            }

            if (isset($act['click']) && $logged_in)
                echo ' onclick="', esc_attr($act['click']), '" ';
            else
                echo ' onclick="return false;" ';
            if (isset($act['title']) && $logged_in)
                echo ' title="', esc_attr($act['title']), '" ';
            else if (!$logged_in)
                echo ' title="', esc_attr__('Please register or log in to perform this action', 'peepso-core'), '" ';
            echo ' href="', ($logged_in ? esc_url($act['href']) : '#'), '" ';
            echo ' class="ps-comment__action ', esc_attr($act['class']), '" ', (isset($act['extra']) ? $act['extra'] : ''), '>';
            echo '<i class="', esc_attr($act['icon']),'"></i>';
            echo '<span>',wp_kses_post($act['label']),'</span>';
            echo '</a>', PHP_EOL;
        }
    }

    /*
     * Output post action options for the post
     */
    public function comment_actions_dropdown()
    {
        global $post;

        $logged_in = is_user_logged_in();		// we're using this a lot, save function overhead

        $acts = array();
        $acts['report'] = array(
            'href' => '#',
            'label' => __('Report', 'peepso-core'),
            'class' => 'actaction-report',
            'icon' => 'gcis gci-flag',
            'click' => 'peepso.post(' . $post->ID . ', ' . $post->act_id . ').doReport();'
        );

        // save comment query
        $comment_query = $this->comment_query;

        $comments_var = $this->get_comments_var($post);
        $comments_batch = $comments_var['comments_batch'];
        $this->get_comments($post->act_external_id, NULL, 1, $comments_batch, $post->act_module_id);
        $this->comment_query->posts = array_reverse($this->comment_query->posts);

        // restore comment query
        $this->comment_query = $comment_query;

        // if it's the post author or an admin - add edit  action
        #if (get_current_user_id() === intval($post->author_id) || PeepSo::is_admin()) {
        if (PeepSo::check_permissions(intval($post->author_id), PeepSo::PERM_POST_EDIT, get_current_user_id())) {
            $acts['edit'] = array(
                'href' => '#edit',
                'label' => __('Edit', 'peepso-core'),
                'class' => 'actaction-edit',
                'icon' => 'gcis gci-edit',
                'click' => 'activity.comment_action_edit(' . $post->ID . ', this); return false;',
            );
        }

        // if it's the post author, owner or an admin - add delete action
        if (PeepSo::check_permissions(intval($post->author_id), PeepSo::PERM_COMMENT_DELETE, get_current_user_id(), false, $post)) {
            $acts['delete'] = array(
                'href' => '#delete',
                'label' => __('Delete comment', 'peepso-core'),
                'class' => 'actaction-delete',
                'icon' => 'gcis gci-trash',
                'click' => 'activity.comment_action_delete(' . $post->ID . '); return false;',
            );

            // if it's the post author, owner or an admin - add remove preview
            $allow_embed = PeepSo::get_option('allow_embed', 1) === 1;

            $show_preview = 0;
            if ($allow_embed && ($post->act_module_id == PeepSoActivity::MODULE_ID))
                $show_preview = get_post_meta($post->ID, '_peepso_display_link_preview', TRUE);
            $media = get_post_meta($post->ID, 'peepso_media', TRUE);
            $visible = ('0' !== $show_preview) && (!empty($media));

            $acts['remove_link_preview'] = array(
                'href' => '#',
                'label' => __('Remove Link Preview', 'peepso-core'),
                'class' => 'actaction-removepreview ps-js-btn-remove-preview',
                'icon' => 'gcis gci-eye-slash', // 'trash',
                'click' => 'return activity.comment_action_remove_preview(' . $post->ID . ');',
                'extra' => $visible ? '' : 'style="display:none"',
            );

        }

        if (! PeepSo::get_option('site_reporting_enable', TRUE) || // global config
            (!is_user_logged_in() && !PeepSo::get_option('site_reporting_allowguest', FALSE)) ||
            get_current_user_id() === intval($post->author_id))	// own content
            unset($acts['report']);

        if (! PeepSo::check_permissions($post->post_author, PeepSo::PERM_POST_LIKE, get_current_user_id()))
            unset($acts['like'], $acts['reply']);

        $acts = apply_filters('peepso_activity_comment_actions', $acts);

        // make sure like action button is not shown on comment actions dropdown.
        unset($acts['like']);

        // if no actions, exit
        if (0 === count($acts))
            return;

        foreach ($acts as $name => $act) {
            echo '<a data-stream-id="', esc_attr($post->ID), '" ';
            if (isset($act['click']) && $logged_in)
                echo ' onclick="', rtrim( esc_js($act['click']), ';' ), '; return false" ';
            else
                echo ' onclick="return false" ';
            if (isset($act['title']) && $logged_in)
                echo ' title="', esc_attr($act['title']), '" ';
            else if (!$logged_in)
                echo ' title="', esc_attr__('Please register or log in to perform this action', 'peepso-core'), '" ';
            if (isset($act['extra']))
                echo ' ', $act['extra'];
            echo ' href="', ($logged_in ? esc_url($act['href']) : '#'), '" ';
            echo ' class="', esc_attr($act['class']), '">';
            echo '<i class="', esc_attr($act['icon']),'"></i>';
            echo '<span>',esc_attr($act['label']),'</span>';
            echo '</a>', PHP_EOL;
        }
    }

    /*
     * Show replies and reply textarea on comment
     * @param PeepSoAjaxResponse $resp The response object
     */
    public function replycomment(PeepSoAjaxResponse $resp) {
        $post_id = $this->_input->int('postid');
        $act_id = $this->_input->int('actid');
        $user_id = $this->_input->int('uid');

        $post = $this->get_comment($post_id);
        if ($post->have_posts()) {
            $post->the_post();
            $this->post_data = get_object_vars($post->post);
        }

        $owner_id = intval($this->get_author_id($post_id));

        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_COMMENT, $user_id)) {

            $html = PeepSoTemplate::exec_template('activity', 'comment-reply', array('post_id' => $post_id, 'post_author' => $user_id, 'act_id' => $act_id, 'PeepSoActivity' => $this), TRUE);

            $resp->set('html', $html);
            $resp->success(1);
        }
    }

    public function show_replycomment($user_id, $post_id, $act_id) {

        if(empty($post_id) || empty($act_id)) {
            return;
        }

        $post = $this->get_comment($post_id);
        if ($post->have_posts()) {
            $post->the_post();
            $this->post_data = get_object_vars($post->post);
        }

        $owner_id = intval($this->get_author_id($post_id));

//        if (PeepSo::check_permissions($owner_id, PeepSo::PERM_COMMENT, $user_id)) {
        PeepSoTemplate::exec_template('activity', 'comment-reply', array('post_id' => $post_id, 'post_author' => $user_id, 'act_id' => $act_id, 'PeepSoActivity' => $this), FALSE);
//        }

    }


    /**
     * Allow add-on to attach content to a comment
     */
    public function comment_attachment()
    {
        global $post;
        global $PeepSoActivityDidInit;

        if(!isset($PeepSoActivityDidInit)) {
            $PeepSoActivityDidInit = TRUE;
            $this->init();
        }

        // let other add-ons have a chance to attach content to the comment
        do_action('peepso_activity_comment_attachment', $post, $post->ID, $post->act_module_id);
    }


    /**
     * Outputs the_content for the current post
     */
    public function content($post = NULL, $echo = TRUE)
    {
        $psnewline = 'PEEPSONEWLINE';
        if(defined('PEEPSO_NEWLINE')) {
            $psnewline = PEEPSO_NEWLINE;
        }

        if (is_null($post))
            global $post;

        $content = strip_tags($post->post_content, '<a>');

        $content = apply_filters('peepso_activity_content_before', $content);

        #5076 add space before linebreak to improve emoji parsing
//        $content = str_replace("\n"," \n", $content);
//        $content = str_replace("  \n"," \n", $content);

        $content = str_replace("\n",$psnewline, $content);

        if(strstr($content, '[')) {
            $content = apply_filters('peepso_activity_remove_shortcode', $content);
        }

        // PeepSo/peepso#2588 do not execute shortcode on peepso activity or comment
        // $content = apply_filters(
        //     'peepso_activity_content',
        //     apply_filters('the_content', $content),
        //     $post
        // );
        //
        // -> change to
        $content = apply_filters(
            'peepso_activity_content',
            convert_smilies($content),
            $post
        );

        $attachments = $this->get_content_attachments($post);

        $attachment_string = '';
        if ( count($attachments) >= 1 ) {
            $content = rtrim($content);

            if ('</p>' === ($markup = substr($content, -4))) {
                $content = substr($content, 0, -4);
            } else {
                $markup = '';
            }

            $attachment_string = ' ' . $this->format_content_attachments($attachments) . $markup;
        }

        // add read-more link on long activity/stream posts and comments

        $doreadmore = array ( PeepSoActivityStream::CPT_POST, PeepSoActivityStream::CPT_COMMENT );

        if(in_array($post->post_type, $doreadmore))
        {
            switch($post->post_type) {
                case PeepSoActivityStream::CPT_POST:
                    $type = "post";
                    break;
                case PeepSoActivityStream::CPT_COMMENT:
                    $type = "comment";
                    break;
                default:
                    $type = "post";
                    break;
            }

            $min_readmore = PeepSo::get_option('site_activity_readmore', 1000);
            $min_readmore_single = PeepSo::get_option('site_activity_readmore_single', 2000);


            if ($min_readmore_single <= $min_readmore) {
                $min_readmore_single = 2 * $min_readmore;
            }


            $length = strlen(wp_strip_all_tags($content));
            $length=0; // #6430 Disables ReadMore logic in PHP completely

            if ($length > $min_readmore && $min_readmore > 0) {

                // default in all scenarios unless processing a stream post on a permalink page
                $is_permalink = PeepSoActivity::is_permalink_ajax();


                // add read-more on all but single activity's POST
                if (!$is_permalink) {
                    $link = ' href="#"';

                    // POST link can lead to a single activity if it's really long
                    if('post' == $type) {
                        if ($length > $min_readmore_single && $min_readmore_single > 0) {
                            $link = ' href="' . $this->post_link(FALSE, $post) . '" data-single-act="1"';
                        }
                    }

                    $excerpt = trim(truncateHtml($content, $min_readmore, '', true, true));

                    $readmorelink ='&hellip; <a class="ps-stream-post-more ps-js-content-excerpt-toggle"' . $link . '>' . __('Read more', 'peepso-core') . '</a>';

                    // attach the read more link in the same line
                    if ('</p>' == substr($excerpt, -4)) {
                        $excerpt = substr($excerpt, 0, -4);
                        $excerpt = $excerpt . $readmorelink ."</p>";
                    } else {
                        $excerpt = $excerpt . $readmorelink;
                    }

                    $wrapper = array(
                        '<div>',
                        '<div class="ps-js-content-excerpt">', $excerpt , '</div>',
                        '<div class="ps-js-content-full" style="display:none">', $content, $attachment_string, '</div>',
                        '</div>'
                    );

                    $content = implode('', $wrapper);
                }
            } else {
                $content .= $attachment_string;
            }
        } else {
            $content .= $attachment_string;
        }


        $content = str_replace($psnewline, "<br/>", $content);

        if ($echo)
            echo $content;
        else
            return ($content);
    }

    /**
     * Get list of attachments for particular post
     * @param WP_Post The current post object
     * @return array List of attachment contents
     */
    public function get_content_attachments($post)
    {
        $args = array(
            'post_id' => $post->ID,
            'attachments' => array()
        );

        $args = apply_filters('peepso_activity_content_attachments', $args);

        return $args['attachments'];
    }

    /**
     * Format post attachments
     * @param array List of post attachments
     * @return string Formatted post attachments
     */
    public function format_content_attachments($attachments)
    {
        $content = '';

        if ( count($attachments) >= 1 ) {
            $glue = ' ' . __('and', 'peepso-core') . ' ';
            $content .= '&mdash; ' . implode($glue, $attachments);
        }

        return ($content);
    }

    /**
     * Displays class used as attribute in any HTML element
     * @param string $class Default class name
     * @param boolean $return_raw If set to TRUE it returns string otherwise it prints the modified class name
     */
    public function content_media_class($class)
    {
        $class = apply_filters('peepso_activity_content_media_class', $class);
        echo esc_attr($class);
    }

    /**
     * Displays the embeded media on the post or comment.
     * - peepso_activity_post_attachment
     * - peepso_activity_comment_attachment
     * @param WP_Post The current post object
     */
    public function content_attach_media($post)
    {
        $allow_embed = PeepSo::get_option('allow_embed', 1) === 1;
        if (!$allow_embed)
            return;

        $show_preview = get_post_meta($post->ID, '_peepso_display_link_preview', TRUE);

        if ('0' === $show_preview)
            return;

        $peepso_media = get_post_meta($post->ID, 'peepso_media');

        if (empty($peepso_media))
            return;

        $peepso_media = apply_filters('peepso_content_media', $peepso_media, $post);
        $link_target = (int) PeepSo::get_option('site_activity_open_links_in_new_tab', 1);
        foreach ($peepso_media as $media) {
            if (isset($media['embed']) && $media['embed']) {
                PeepSoTemplate::exec_template('activity', 'content-embed', $media['embed']);
                continue;
            }

            if (!isset($media['url']) || !isset($media['description']))
                continue;

            $url = parse_url($media['url']);
            if('https' != $url['scheme'] && !PeepSo::get_option('allow_non_ssl_embed', 0)) {
                update_post_meta($post->ID, '_peepso_display_link_preview', 0);
                continue;
            }

            $media['target'] = '';
            if (1 === $link_target) {
                $media['target'] = 'target="_blank"';
            } else if (2 === $link_target && 0 !== strpos($url['host'], site_url())) {
                $media['target'] = 'target="_blank"';
            }

            $media['host'] = parse_url($media['url'], PHP_URL_HOST);

            // make iframe full-width
            $media['content'] = isset($media['content']) ? $media['content'] : '';
            if (preg_match('/<iframe/i', $media['content'])) {
                $width_pattern = "/width=\"[0-9]*\"/";
                $media['content'] = preg_replace($width_pattern, "width='100%'", $media['content']);
                $media['content'] = apply_filters('the_content', $media['content']);
                $media['content'] = '<div class="ps-media ps-media--iframe ps-media-iframe">' . $media['content'] . '</div>';
            }

            // Improve Facebook embedded content rendering.
            if (preg_match('#class="fb-(post|video)"#i', $media['content'])) {

                // Remove Facebook SDK loader code.
                $media['content'] = preg_replace('#<div[^>]+id="fb-root"[^<]+</div>#i', '', $media['content']);
                $media['content'] = preg_replace('#<script[^<]+</script>#i', '', $media['content']);

                // Remove width setting, follow container width.
                // #1931 Fix Facebook video issue.
                $media['content'] = preg_replace('#\sdata-width=["\']\d+%?["\']#i', '', $media['content']);
            }

            PeepSoTemplate::exec_template('activity', 'content-media', $media);
        }
    }

    /*
     * loads the poststatus template
     */
    public function post_status()
    {
        PeepSoTemplate::exec_template('activity', 'poststatus');

        wp_enqueue_script('peepso-activitystream');
    }


    /*
     * checks whether the query has any remaining posts
     * returns only the first page
     * @return boolean TRUE if there are more posts in the query
     */
    public function has_posts($stream_id = 1, $pinned = FALSE, $limit=1)
    {
        $this->stream_id = $stream_id;

        if (NULL === $this->post_query || $pinned != $this->pinned) {
            $this->pinned = $pinned;
            if (PeepSo::get_option('site_activity_hide_stream_from_guest', 0) && FALSE === is_user_logged_in()) {
                return (0);
            }

            $owner = apply_filters('peepso_user_profile_id', 0);
            $user = get_current_user_id();
            $this->get_posts(NULL, $owner, $user, 1, $pinned, $limit);
        }
        return ($this->post_query->have_posts());
    }


    /*
     * sets up the next post from the result set to be used with the templating system
     * @return Boolean TRUE on success with a valid post ready; FALSE otherwise
     */
    public function next_post()
    {
        if ($this->post_query->have_posts()) {
            if ($this->post_query->current_post >= $this->post_query->post_count)
                return (FALSE);

            $this->post_query->the_post();
            $this->post_data = get_object_vars($this->post_query->post);
            return (TRUE);
        }
        return (FALSE);
    }

    /* display post age
     */
    public function post_age($print = TRUE)
    {
        ob_start();
        // config
        $config_date_format = get_option('date_format'). ' ' . get_option('time_format');
        $config_absolute_dates = PeepSo::get_option('absolute_dates', 0);


        // GMT post date and current date
        $post_date = get_post_time('U', TRUE);
        $curr_date = current_time('timestamp', TRUE);

        // post time & time adjusted to user's timezone
        $post_timestamp_user_offset = $post_date + 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());
        $post_time_user_offset = date("Y-m-d H:i:s", $post_timestamp_user_offset); // #5154 used mysql format for mysql2date

        // scheduled post
        if($post_date > $curr_date) {

            echo esc_attr(sprintf(__('Scheduled for %s','peepso-core'), date_i18n($config_date_format, $post_timestamp_user_offset)));

        } else {

            $use_absolute = FALSE;

            if(0 == $config_absolute_dates) {
                $use_absolute = TRUE;
            } elseif(-1==$config_absolute_dates) {
                $use_absolute = FALSE;
            } elseif($config_absolute_dates > 0) {
                if($config_absolute_dates * 3600 > ($curr_date - $post_timestamp_user_offset)) {
                    $use_absolute = TRUE;
                } else {
                    $use_absolute = FALSE;
                }
            }

            if ($use_absolute) {
                echo '<span class="ps-js-autotime" data-timestamp="', esc_attr(get_the_time('U')), '" title="', esc_attr(mysql2date($config_date_format, $post_time_user_offset)), '">', esc_attr(PeepSoTemplate::time_elapsed($post_date, $curr_date)), '</span>';
            } else {
                echo esc_attr(mysql2date($config_date_format, $post_time_user_offset));
            }
        }

        if(!$print) {
            return ob_get_clean();
        }

        echo ob_get_clean();
    }

    public function post_permalink(){
        echo '<span class="ps-tooltip ps-tooltip--permalink ps-js-permalink" data-tooltip="' .
            esc_attr__( 'Click to copy', 'peepso-core' ) . '" data-tooltip-initial="' .
            esc_attr__( 'Click to copy', 'peepso-core' ) . '" data-tooltip-success="' .
            esc_attr__( 'Copied.', 'peepso-core' ) . '"><i class="gcis gci-link"></i></span>';
    }

    public function post_edit_notice() {

        $config_date_format = get_option('date_format'). ' ' . get_option('time_format');

        if(!get_current_user_id()) return;
        if(!PeepSo::get_option('post_edit_notice_show',1)) return;

        global $post;

        if(PeepSo3_Utilities_String::maybe_strlen($edit_date = get_post_meta($post->ID, 'peepso_last_edit', TRUE))){ ?>

            <?php
            $edit_date_user_offset = date_i18n($config_date_format, $edit_date + 3600 * PeepSoUser::get_gmt_offset(get_current_user_id()));
            ?>

            <span class="ps-post__edited ps-stream-edit-notice  ps-tooltip"  data-tooltip="<?php echo esc_attr(sprintf(__('Last edited %s','peepso-core'), $edit_date_user_offset)); ?>">
                <i class="gcis gci-edit"></i>
            </span>
            <?php
        }
    }

    /*
     * Output post action options for the post
     */
    public function post_actions()
    {
        global $post;

        $like = $this->get_like_status($post->act_external_id, $post->act_module_id);
        $logged_in = is_user_logged_in();		// we're using this a lot, save function overhead

        $acts = array(
            'like' => array(
                'href' => '#like',
                'label' => $like['label'],
                'class' => 'ps-post__action ps-post__action--reaction actaction-like' . ( $like['liked'] ? ' liked' : '' ),
                'icon' => $like['icon'],
                'click' => 'return activity.action_like(this, ' . $post->act_id . ');',
                'count' => $like['count'],
            ),
        );

        $current_user_id = get_current_user_id();
        if (PeepSo::get_option('activity_social_sharing_enable', 0)) {
            $repost_enabled = PeepSo::get_option('site_repost_enable', true);
            $popup_position = PeepSo::get_option('site_repost_position', 'context_menu') === 'sharing_popup';

            $is_group_post = class_exists('PeepSoGroupsPlugin') && $post->act_module_id == PeepSoGroupsPlugin::MODULE_ID;
            $is_page_post  = class_exists('PeepSoPagesPlugin') && $post->act_module_id == PeepSoPagesPlugin::MODULE_ID;
            $page_repost   = defined('PEEPSO_PAGE_REPOST');

            $can_repost_page = !$is_page_post || ($page_repost && $is_page_post);
            $is_author      = intval($post->post_author) === $current_user_id;
            $can_repost_user = ($logged_in && !$is_author)
                            || ($page_repost && $is_page_post)
                            || PeepSo::is_admin();
            $show_repost = $repost_enabled &&
                $popup_position &&
                !$is_group_post &&
                $can_repost_page &&
                $can_repost_user;
            $shared_url = PeepSo::get_page('activity_status') . $post->post_title . '/';
            $acts['share'] = array(
                'href' => '#share',
                'label' => __('Share', 'peepso-core'),
                'class' => 'ps-post__action ps-post__action--share actaction-share',
                'icon' => 'gcis gci-share-alt',
                'click' => "share.share_url('{$shared_url}', { act_id: '{$post->act_id}', show_repost: '{$show_repost}' }); return false;",
                'allow_guest' => TRUE
            );
        }

        $repost_enabled = PeepSo::get_option('site_repost_enable', true);
        $can_repost = ($logged_in && intval($post->post_author) !== $current_user_id) || 
            (class_exists('PeepSoPagesPlugin') && defined('PEEPSO_PAGE_REPOST') && $post->act_module_id == PeepSoPagesPlugin::MODULE_ID) ||
            PeepSo::is_admin();
        $position = PeepSo::get_option('site_repost_position', 'context_menu');
        if (
            $repost_enabled &&
            $can_repost &&
            (
                $position === 'post_footer' ||
                (
                    $position === 'sharing_popup' &&
                    !PeepSo::get_option('activity_social_sharing_enable', 0)
                )
            )
        ) {
            $acts['repost'] = array(
                'href' => '#repost',
                'label' => __('RePost', 'peepso-core'),
                'class' => 'ps-post__action ps-post__action--repost actaction-repost',
                'icon' => 'gcis gci-share-square',
                'click' => 'return activity.action_repost(' . $post->act_id . ', this);',
                'allow_guest' => FALSE
            );
        }

        $mode = PeepSo::get_option('post_view_count_show', 0);

        if ($mode && isset($this->post_data['act_id'])) {

            $general_count = PeepSoActivityRanking::get_view_count($this->post_data['act_id']);
            $unique_count = PeepSoActivityRanking::get_unique_view_count($this->post_data['act_id']);


            $count = PeepSoTemplate::exec_template('activity','view-count', array('mode'=>$mode, 'general'=>$general_count, 'unique'=>$unique_count), TRUE);

            $acts['views'] = array(
                'href' => '',
                'label' => $count,
                'class' => 'ps-post__action ps-post__action--views ps-post__action--views-act-id-'.$this->post_data['act_id'],
                'icon' => 'gcis gci-eye post_view_count_show-'.$mode,

            );
        }

        if ($logged_in && !is_null($this->post_data)) {
            $notif = new PeepSo3_Activity_Notifications($this->post_data['act_external_id'], get_current_user_id());
            $acts['follow'] = array(
                'href' => '#follow',
                'label' => __('Follow', 'peepso-core'),
                'class' => 'ps-post__action ps-post__action--save actaction-save ps-loading-pulse ps-js-follow-toggle',
                'icon' => 'gcir gci-square-check',
                'data-post-id' => $this->post_data['act_external_id'],
                'data-follow' => $notif->is_following()
            );
        }

        if ($logged_in && PeepSo::get_option_new('post_save_enable')) {
            $acts['save'] = array(
                'href' => '#save',
                'label' => __('Save', 'peepso-core'),
                'class' => 'ps-post__action ps-post__action--save actaction-save ps-loading-pulse ps-js-save-toggle',
                'icon' => 'gcir gci-bookmark'
            );
        }

        $options = apply_filters('peepso_activity_post_actions', array('acts'=>$acts,'post'=>$post));
        $acts = $options['acts'];

        if (! PeepSo::check_permissions($post->post_author, PeepSo::PERM_POST_LIKE, get_current_user_id())) {
            unset($acts['like']);
        }

        ob_start();
        do_action('peepso_post_inside_actions');
        $reactions = ob_get_clean();

        if (0 === count($acts) && empty(trim(strip_tags($reactions)))) {
            // if no options, exit
            return;
        }

        echo '<div class="ps-post__actions stream-actions js-stream-actions" data-type="stream-action">', PHP_EOL;
        echo '<div class="ps-post__actions-inner">', PHP_EOL;
        echo $reactions;
        $this->_display_post_actions($post->act_id, $acts);
        echo '</div>', PHP_EOL;
        echo '</div>', PHP_EOL;
    }


    /**
     * Echo the html for activity feed actions.
     * @param  int $post_id The post ID.
     * @param  array $acts  The list of actions with labels and click methods.
     */
    private function _display_post_actions($post_id, $acts)
    {
        $logged_in = is_user_logged_in();		// we're using this a lot, save function overhead
        foreach ($acts as $name => $act) {
            if ($name == 'like' && !apply_filters('peepso_permissions_reactions_create', TRUE)) {
                continue;
            }
            $allow_guest = $logged_in || ( isset($act['allow_guest']) && $act['allow_guest'] );

            $element = 'a';

            if(!isset($act['href']) || !strlen($act['href'])) {
                $act['href'] = '';
                $element = 'div';
            }

            echo '<' . esc_attr($element) .' data-stream-id="', esc_attr($post_id), '" ';

            // Prints embedded data.
            $act_data = array_filter($act, function($k) { return 0 === strpos($k, 'data-'); }, ARRAY_FILTER_USE_KEY);
            foreach ($act_data as $key => $value) {
                echo ' '. $key .'="' . esc_attr($value) . '"';
            }

            if (isset($act['click']) && $allow_guest)
                echo ' onclick="', esc_attr($act['click']), '" ';
            else
                echo ' onclick="return false;" ';
            if (isset($act['title']) && $allow_guest)
                echo ' title="', esc_attr($act['title']), '" ';
            else if (!$allow_guest)
                echo ' title="', esc_attr__('Please register or log in to perform this action', 'peepso-core'), '" ';
            echo ' href="', ($allow_guest ? esc_url($act['href']) : '#'), '" ';
            echo ' class="', (isset($act['class']) ? esc_attr($act['class']) : ''),'" ';
            echo isset( $act['style'] ) ? ( ' style="' . esc_attr($act['style']) .'"' ) : '';
            echo '>';
            if (isset($act['icon']))
                echo '<i class="',esc_attr($act['icon']),'"></i>';
            if (isset($act['label']))
                echo '<span>',wp_kses_post($act['label']),'</span>';
            echo '</' . esc_attr($element) .'>', PHP_EOL;
        }
    }


    /*
     * Creates a drop-down menu of post options, user and context appropriate
     */
    public function post_options($post = NULL)
    {
        /**
         * Edit
         * Reschedule
         * Delete
         *
         * Repost
         *
         * Pin
         * NSFW
         * Disable Comments
         * Remove link preview
         *
         *
         * Report
         */

        $as_array = FALSE;
        if(NULL == $post) {
            global $post;
        } else {
            $as_array = TRUE;
        }
        $logged_in = is_user_logged_in();
        if (!$logged_in ||
            ($post->act_owner_id === get_current_user_id() || $post->post_author === get_current_user_id()) )
            return;

        // current user is post owner

        $user_id = get_current_user_id();
        $options = array();
        // only add this if current_user == owner_id, it's an admin or other plugin allows it

        // Add toggle post header.
        $options = $this->post_options_add_toggle_header($options, $post);

        // Edit & Reschedule
        if (PeepSo::check_permissions(intval($post->author_id), PeepSo::PERM_POST_EDIT, $user_id)) {

            // Edit
            $options['edit'] = array(
                'label' => __('Edit', 'peepso-core'),
                'icon' => 'gcis gci-edit',
                'click' => 'activity.option_edit(' . $post->ID . ', ' . $post->act_id . '); return false',
            );

            // Reschedule
            if(PeepSo::is_dev_mode('reschedule')) {
                $options['reschedule'] = array(
                    'label' => __('Change date & time', 'peepso-core'),
                    'icon' => 'gcir gci-clock',
                    'click' => 'return activity.TBD(' . $post->ID . ', ' . $post->act_id . '); return false',
                );
            }
        }

        // Delete
        if (PeepSo::check_permissions(intval($post->author_id), PeepSo::PERM_POST_DELETE, $user_id) ||
            PeepSo::check_permissions(intval($post->act_owner_id), PeepSo::PERM_POST_DELETE, $user_id)) {

            $options['delete'] = array(
                'label' => __('Delete', 'peepso-core'),
                'icon' => 'gcis gci-trash', // 'remove',
                'click' => 'return activity.action_delete(' . $post->ID . ');',
            );
        }

        // Spacer
        $options['spacer01'] = NULL;

        // Repost
        $repost_enabled = PeepSo::get_option('site_repost_enable', true);
        $can_repost = ($logged_in && intval($post->post_author) !== $user_id) || 
            (class_exists('PeepSoPagesPlugin') && defined('PEEPSO_PAGE_REPOST') && $post->act_module_id == PeepSoPagesPlugin::MODULE_ID) ||
            PeepSo::is_admin();
        $position = PeepSo::get_option('site_repost_position', 'context_menu');
        if (
            $repost_enabled &&
            $can_repost &&
            $position === 'context_menu'
        ) {
            $options['repost'] = array(
                'href' => '#repost',
                'label' => __('RePost', 'peepso-core'),
                'icon' => 'gcis gci-share-square',
                'click' => 'return activity.action_repost(' . $post->act_id . ', this);',
            );
        }

        // Spacer
        $options['spacer02'] = NULL;

        // Pin
        if(PeepSo::get_option_new('pinned_posts_enable')) {
            if (PeepSo::can_pin(intval($post->ID))) {
                $options['pin'] = [
                    'label' => __('Pin...', 'peepso-core'),
                    'icon' => 'gcis gci-thumbtack', // 'remove',
                    'click' => 'return false;',
                    'li-class' => 'parent', // show the sub-items
                ];

                $options['sub_pin_indefinitely'] = [
                    'label' => __('Pin indefinitely', 'peepso-core'),
                    'icon' => 'gcis gci-thumbstack-infinity', // 'remove',
                    'click' => 'return activity.action_pin(' . $post->ID . ', 1);',
                    'li-class' => 'child pin',
                ];

                $options['sub_pin_date'] = [
                    'label' => __('Pin until...', 'peepso-core'),
                    'icon' => 'gcis gci-thumbstack-clock', // 'remove',
                    'click' => 'return activity.action_pin_dialog(' . $post->ID . ');', // date picker
                    'li-class' => 'child pin',
                ];

                if ($this->is_pinned($post)) {
                    $options['unpin'] = [
                        'label' => __('Unpin', 'peepso-core'),
                        'icon' => 'gcis gci-thumbtack-slash', // 'remove',
                        'click' => 'return activity.action_pin(' . $post->ID . ', 0);',
                    ];
                }
            }

            if ($this->is_pinned($post)) {

                $pinned_by = get_post_meta($post->ID, 'peepso_pinned_by', TRUE);
                $pinned = get_post_meta($post->ID, 'peepso_pinned', TRUE);

                if (PeepSo3_Utilities_String::maybe_strlen($pinned_by)) {

                    $PeepSoUser = PeepSoUser::get_instance($pinned_by);

                    $pinned_date = $pinned + 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());

                    $pinned_by_abbr = sprintf('<abbr title="%s">%s</abbr>',$PeepSoUser->get_fullname(),  $PeepSoUser->get_firstname());

                    $pinned_by_label = sprintf(__('Pinned by %s', 'peepso-core'), $pinned_by_abbr);

                    $format = PeepSo::get_option('date_format_no_year', 'F j');
                    if('custom' == $format) {
                        $format = PeepSo::get_option('date_format_no_year_custom', 'F j');
                    }

                    $pinned_date_full = date_i18n(get_option('date_format'), $pinned_date) . ' @ ' . date_i18n(get_option('time_format'), $pinned_date);
                    $pinned_date_short = date_i18n($format, $pinned_date);
                    $pinned_abbr = "<abbr title=\"$pinned_date_full\">" . $pinned_date_short . "</abbr>";
                    $pinned_on_label = sprintf(__('Pinned %s', 'peepso-core'), $pinned_abbr);

                    $options['pin_details'] = [
                        'label' => __('More info...','peepso-core'),
                        'icon' => 'gcis gci-info-circle',
                        'click' => 'return false',
                        'li-class' => 'parent', // show the sub-items
                    ];

                    $options['pinned_by'] = array(
                        'label' => $pinned_by_label,
                        'icon' => 'gcis gci-info-circle',
                        'click' => 'window.open("' . $PeepSoUser->get_profileurl() . '", "_blank");return false',
                        'li-class' => 'child pin_details ps-js-label-pinned-by',
                    );

                    $options['pinned_on'] = array(
                        'label' => $pinned_on_label,
                        'icon' => 'gcis gci-info-circle',
                        'click' => 'return false',
                        'li-class' => 'child pin_details ps-js-label-pinned-on',
                    );
                }

                if (PeepSo::can_pin(intval($post->ID))) {

                    $pinned_until = get_post_meta($post->ID, 'peepso_pinned_until', TRUE);
                    $pinned_until_label = __('Pinned indefinitely', 'peepso-core');

                    if (PeepSo3_Utilities_String::maybe_strlen($pinned_until) && is_numeric($pinned_until)) {
                        $pinned_until = intval($pinned_until);
                        $pinned_until = $pinned_until + 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());


                        $pinned_until_date_full = date_i18n(get_option('date_format'), $pinned_until) . ' @ ' . date_i18n(get_option('time_format'), $pinned_until);
                        $pinned_until_date_short = date_i18n($format, $pinned_until);
                        $pinned_until_abbr = "<abbr title=\"$pinned_until_date_full\">" . $pinned_until_date_short . "</abbr>";
                        $pinned_until_label = sprintf(__('Pinned until %s', 'peepso-core'), $pinned_until_abbr);

                    }

                    $options['pinned_until'] = array(
                        'label' => $pinned_until_label,
                        'icon' => 'gcis gci-info-circle',
                        'click' => 'return false',
                        'li-class' => 'child pin_details ps-js-label-pinned-until',
                    );
                }

                $options['spacer_pinned'] = NULL;

            }
        }

        // NSFW
        if(PeepSo::get_option_new('nsfw') && PeepSo::can_nsfw($post->ID)) {
            $options['nsfw'] = array(
                'label' => __('Mark as sensitive', 'peepso-core'),
                'icon' => 'gcis gci-exclamation-circle',
                'click' => 'activity.action_nsfw(' . $post->ID . ', 1);',
            );

            if ($this->is_nsfw($post)) {
                $options['nsfw'] = array(
                    'label' => __('Not sensitive', 'peepso-core'),
                    'icon' => 'gcis gci-check-circle',
                    'click' => 'activity.action_nsfw(' . $post->ID . ', 0);',
                );
            }
        }

        // Add enable/disable comments toggle.
        $options = $this->post_options_add_toggle_comments($options, $post);

        // Remove link preview
        if (get_current_user_id() === intval($post->author_id) || get_current_user_id() === intval($post->act_owner_id) || PeepSo::is_admin()) {
            $allow_embed = PeepSo::get_option('allow_embed', 1) === 1;

            if ($allow_embed) {
                $show_preview = get_post_meta($post->ID, '_peepso_display_link_preview', TRUE);
                $media = get_post_meta($post->ID, 'peepso_media', TRUE);
                $visible = ('0' !== $show_preview) && (!empty($media));

                $options['remove_link_preview'] = array(
                    'href' => '#',
                    'label' => __('Remove Link Preview', 'peepso-core'),
                    'li-class' => 'ps-js-btn-remove-preview',
                    'icon' => 'gcis gci-eye-slash',
                    'click' => 'activity.action_remove_link_preview(' . $post->ID . ', ' . $post->act_id . ');',
                    'extra' => $visible ? '' : 'style="display:none"',
                );
            }
        }

        // Spacer
        $options['spacer03'] = NULL;

        // Report
        if (intval($post->post_author) !== $user_id) {

            if (PeepSo::get_option('site_reporting_enable', TRUE)) {
                $options['report'] = array(
                    'href' => '#',
                    'label' => __('Report', 'peepso-core'),
                    'icon' => 'gcis gci-exclamation-triangle',
                    'click' => 'peepso.post(' . $post->ID . ', ' . $post->act_id . ').doReport();',
                );
            }
        }

        if( isset($options['repost']) && FALSE === apply_filters('peepso_permissions_repost_create', TRUE)) {
            unset($options['repost']);
        }

        $options = apply_filters('peepso_post_filters', array('acts'=>$options,'post'=>$post));

        if($as_array) {
            return $options;
        }
        // if no options to display, exit
        if (!is_array($options) || 0 === count($options)) {
            return;
        }

        echo '<div class="ps-post__options-menu ps-js-dropdown">', PHP_EOL;
        echo    '<a href="#" class="ps-dropdown__toggle ps-js-dropdown-toggle" data-value="">', PHP_EOL;
        echo        '<span class="gcis gci-ellipsis-h"></span>', PHP_EOL;
        echo    '</a>', PHP_EOL;

        echo    '<div class="ps-dropdown__menu ps-js-dropdown-menu">', PHP_EOL;

        $optionsHtml = '';
        foreach ($options as $name => $data) {
            if (NULL === $data) {
                $optionsHtml .= '__spacer__';
            } else {
                $optionsHtml .= '<a href="#"';
                if (isset($data['li-class']))
                    $optionsHtml .= ' class="' . $data['li-class'] . '"';
                if (isset($data['extra']))
                    $optionsHtml .= ' ' . $data['extra'];
                if (isset($data['click']))
                    $optionsHtml .= ' onclick="' . rtrim( esc_js($data['click']), ';' ) . '; return false"';
                $optionsHtml .= ' data-post-id="' . $post->ID . '">';
                $optionsHtml .= '<i class="' . $data['icon'] . '"></i><span>' . $data['label'] . '</span></a>';
            }
        }

        // Removes leading, trailing, and sequential separator placeholders.
        $optionsHtml = preg_replace('/^(__spacer__)+/', '', $optionsHtml);
        $optionsHtml = preg_replace('/(__spacer__)+$/', '', $optionsHtml);
        $optionsHtml = preg_replace('/(__spacer__){2,}/', '', $optionsHtml);

        // Replace separator placeholders with actual html.
        $optionsHtml = preg_replace('/__spacer__/', '<span class="ps-post__options-sep"></span>', $optionsHtml);

        echo        $optionsHtml;
        echo    '</div>', PHP_EOL;
        echo '</div>', PHP_EOL;
    }

    public function toggle_post_header(PeepSoAjaxResponse $resp) {
        $act_id = $this->_input->int('act_id');
        $post_id = $this->_input->int('post_id');

        if (PeepSo::is_admin()) {
            $is_header_hidden = get_post_meta($post_id, 'peepso_hide_post_header', true);

            if ($is_header_hidden) {
                delete_post_meta($post_id, 'peepso_hide_post_header');
            } else {
                add_post_meta($post_id, 'peepso_hide_post_header', true);
            }

            $resp->success(TRUE);
        } else {
            $resp->success(FALSE);
            $resp->error(__('You don\'t have permissions for that ', 'peepso-core') . $user_id . '/' . $owner_id);
        }
    }

    /**
     * Adds enable/disable comments toggle if necessary.
     *
     * @param array $options
     * @param object $post
     * @return array
     */
    public function post_options_add_toggle_comments($options, $post) {

        if($this->can_disable_comments($post->ID)) {
            $option_comment_enable = array(
                'label' => __('Enable comments', 'peepso-core'),
                'icon' => 'gcis gci-comment-medical',
                'click' => 'activity.option_enable_comments(' . $post->ID . ', this); return false;',
            );

            $option_comment_disable = array(
                'label' => __('Disable comments', 'peepso-core'),
                'icon' => 'gcis gci-comment-slash',
                'click' => 'activity.option_disable_comments(' . $post->ID . ', this); return false;',
            );

            $option_data_extra = ' data-opt-enable="' . esc_attr(json_encode($option_comment_enable)) . '"';
            $option_data_extra .= ' data-opt-disable="' . esc_attr(json_encode($option_comment_disable)) . '"';

            if (PeepSo3_Utilities_String::maybe_strlen(get_post_meta($post->ID, 'peepso_disable_comments', TRUE))) {
                $options['comments_disable'] = array_merge(
                    $option_comment_enable,
                    array('li-class' => 'ps-js-opt-toggle-comments', 'extra' => $option_data_extra)
                );

            } else {
                $options['comments_disable'] = array_merge(
                    $option_comment_disable,
                    array('li-class' => 'ps-js-opt-toggle-comments', 'extra' => $option_data_extra)
                );
            }
        }

        return $options;
    }

    /**
     * Adds toggle post header if necessary.
     *
     * @param array $options
     * @param object $post
     * @return array
     */
    public function post_options_add_toggle_header($options, $post) {
        if (PeepSo::get_option_new('allow_hide_header') && PeepSo::is_admin()) {
            $option_show_header = array(
                'label' => __('Show post header', 'peepso-core'),
                'icon' => 'gcis gci-eye',
                'click' => 'activity.option_toggle_post_header(' . $post->ID . ', ' . $post->act_id . '); return false',
            );

            $option_hide_header = array(
                'label' => __('Hide post header', 'peepso-core'),
                'icon' => 'gcis gci-eye-slash',
                'click' => 'activity.option_toggle_post_header(' . $post->ID . ', ' . $post->act_id . '); return false',
            );

            $option_data_extra = ' data-opt-enable="' . esc_attr(json_encode($option_show_header)) . '"';
            $option_data_extra .= ' data-opt-disable="' . esc_attr(json_encode($option_hide_header)) . '"';

            if (get_post_meta($post->ID, 'peepso_hide_post_header', true)) {
                $options['hide_header'] = array_merge(
                    $option_show_header,
                    array('li-class' => 'ps-js-opt-toggle-header', 'extra' => $option_data_extra)
                );
            } else {
                $options['hide_header'] = array_merge(
                    $option_hide_header,
                    array('li-class' => 'ps-js-opt-toggle-header', 'extra' => $option_data_extra)
                );
            }

            $options['spacer00'] = NULL;
        }

        return $options;
    }

    public function is_pinned($post)
    {
        $pinned = get_post_meta($post->ID, 'peepso_pinned', TRUE);

        return PeepSo3_Utilities_String::maybe_strlen($pinned);
    }

    public function is_nsfw($post) {
        $nsfw = get_post_meta($post->ID, 'peepso_nsfw', TRUE);

        return PeepSo3_Utilities_String::maybe_strlen($nsfw);
    }

    /*
     * Display the permalink href= link for the given post
     */
    public function post_link($echo = TRUE, $post = FALSE)
    {
        if (!$post)
            global $post;

        $link = PeepSo::get_page('activity_status') . $post->post_title . '/';
        if ($echo)
            echo esc_url($link);
        else
            return ($link);
    }

    /*
     * Display the permalink href= link for the given comment
     */
    public function comment_link($echo = TRUE, $post = FALSE)
    {
        if (!$post)
            global $post;

        $orig_act_data = $this->get_activity_data($post->ID, self::MODULE_ID);

        $parent_act_data = $this->get_activity_data($orig_act_data->act_comment_object_id, $orig_act_data->act_comment_module_id);
        $parent_orig_post = $this->get_activity_post($parent_act_data->act_id);

        $link = PeepSo::get_page('activity_status') . $parent_orig_post->post_title . '/' . '#comment.' . $parent_act_data->act_id . '.' . $post->ID . '.' . $post->ID;

        // comment reply
        if ($parent_orig_post->post_type == PeepSoActivityStream::CPT_COMMENT) {
            $comment_activity = $this->get_activity_data($parent_act_data->act_comment_object_id, $parent_act_data->act_comment_module_id);
            $parent_comment = $this->get_activity_post($comment_activity->act_id);

            $link = PeepSo::get_page('activity_status') . $parent_comment->post_title . '/' . '?t=' . time() . '#comment.' . $comment_activity->act_id . '.' . $parent_orig_post->ID . '.' . $parent_act_data->act_id . '.' . $orig_act_data->act_external_id;
        }

        if ($echo)
            echo esc_url($link);
        else
            return ($link);
    }

    /**
     * Get the root post
     */
    public function root_post($post = FALSE) {
        if (!$post) {
            return FALSE;
        }

        // check if post object is a comment
        $root = $post;
        if ($post->post_type == PeepSoActivityStream::CPT_COMMENT && $post->act_comment_module_id == self::MODULE_ID) {
            $root_activity = $this->get_activity_data($post->act_comment_object_id, $post->act_comment_module_id);
            $root = $this->get_activity_post($root_activity->act_id);

            // if it's root is a comment
            if ($root->post_type == PeepSoActivityStream::CPT_COMMENT && $post->act_comment_module_id == self::MODULE_ID) {
                return $this->root_post($root);
            }
        }

        if ($post->post_type == PeepSoActivityStream::CPT_POST) {
            $root = $this->get_activity_post($root->act_id);
        }

        // handle for another PeepSo plugins
        if ($root->act_comment_module_id != self::MODULE_ID) {
            $root = apply_filters('peepso_root_post_' . $root->act_comment_module_id, $root);
        }

        return $root;
    }


    /**
     * Get the root object
     */
    public function root_object($post = FALSE) {
        if (!$post) {
            return FALSE;
        }

        // check if post object is a comment
        $root = $post;
        if ($post->post_type == PeepSoActivityStream::CPT_COMMENT && $post->act_comment_module_id == self::MODULE_ID) {
            $root_activity = $this->get_activity_data($post->act_comment_object_id, $post->act_comment_module_id);
            $root = $this->get_activity($root_activity->act_id);

            // if it's root is a comment
            if ($root->post_type == PeepSoActivityStream::CPT_COMMENT && $post->act_comment_module_id == self::MODULE_ID) {
                return $this->root_object($root);
            }
        }

        if ($post->post_type == PeepSoActivityStream::CPT_POST) {
            $root = $this->get_activity($root->act_id);
        }

        // handle for another PeepSo plugins
        if ($root->act_comment_module_id != self::MODULE_ID && $root->act_comment_module_id > 0) {
            $root = apply_filters('peepso_root_object_' . $root->act_comment_module_id, $root);
        }

        return $root;
    }

    /*
     * Output the post's modified time as a GMT based timestamp
     */
    public function post_timestamp()
    {
        the_time('U');
    }


    /*
     * Allows other add-ons to output their post-specific data
     */
    public function post_attachment()
    {
        global $post;
        global $PeepSoActivityDidInit;

        if(!isset($PeepSoActivityDidInit)) {
            $PeepSoActivityDidInit = TRUE;
            $this->init();
        }

        // let other add-ons have a chance to attach content to the post
        do_action('peepso_activity_post_attachment', $post, $post->ID, $post->act_module_id);
    }


    /**
     * Displays the activity privacy icon set on a post.
     */
    public function post_access()
    {
        global $post;

        $privacy = PeepSoPrivacy::get_instance();
        $level = $privacy->get_access_setting($post->act_access);

        echo '<i class="', esc_attr($level['icon']), '"></i><span>', esc_attr($level['label']) ,'</span>';
    }

    /*
     * outputs the contents of a single post
     */
    public function show_post($no_html = FALSE)
    {
        $url = PeepSoUrlSegments::get_instance();
        $post_slug = sanitize_key($url->get(2));

        // Raw view counter is display-only (excluded from every engagement score).
        // Only write it when view counts are actually shown, to avoid an UPDATE on
        // every single post render.
        if (PeepSo::get_option('post_view_count_show', 0)) {
            $rank = new PeepSoActivityRanking();
            $rank->add_view_count($this->post_data['act_id']);
        }

        if (!empty($post_slug)) {
            // if post type is comment and permalink, redirect to parent activity
            $parent_post = get_post($this->post_data['act_comment_object_id']);
            if ($this->post_data['post_type'] === PeepSoActivityStream::CPT_COMMENT) {
                PeepSo::redirect(PeepSo::get_page('activity_status') . $parent_post->post_name);
                exit;
            }
        }

        // Refresh media
        $refresh = PeepSo::get_option('refresh_embeds');
        if( $refresh > 0 && empty(PeepSo3_Mayfly::get($mayfly = 'peepso_cache_media_'.$this->post_data['ID']))) {
            // #5199 reset peepso_media to prevent duplicate embed
            $this->peepso_media = array();
            new PeepSoError('Refreshing post '.$this->post_data['ID']);

            $post_content = $this->post_data['post_content'];
            add_filter('oembed_result', array(&$this, 'oembed_result'), 10, 3);
            $filtered_content = apply_filters('peepso_activity_post_content', $post_content, $this->post_data['ID']);
            remove_filter('oembed_result', array(&$this, 'oembed_result'));

            $this->handle_embed_data();
            $this->save_peepso_media($this->post_data['ID']);

            // Add several seconds of randomness between perishables, to avoid slowing down the entire stream at once
            PeepSo3_Mayfly::set($mayfly,1,$refresh+rand(30,60));
        }

        $this->post_data['nsfw'] = get_post_meta($this->post_data['ID'],'peepso_nsfw', TRUE);
        $this->post_data['pinned'] = get_post_meta($this->post_data['ID'],'peepso_pinned', TRUE);
        $this->post_data['pinned_by'] = get_post_meta($this->post_data['ID'],'peepso_pinned_by', TRUE);
        $this->post_data['pinned_date'] = get_post_meta($this->post_data['ID'],'peepso_pinned_date', TRUE);

        $this->post_data['human_friendly'] = PeepSo3_Utilities_String::maybe_strlen($human_friendly = get_post_meta($this->post_data['ID'], 'peepso_human_friendly', TRUE)) ? $human_friendly : FALSE;

        $no_html = apply_filters('peepso_show_post_no_html', $no_html, $this->post_data);

        $this->post_data['reported'] = 0;
        $this->post_data['reports'] = [];

        if(PeepSo::is_admin()) {
            $this->post_data['reports'] = (new PeepSoReport())->get_reports( $this->post_data['ID'], $this->post_data['act_module_id'], 0);
            if(is_array($this->post_data['reports']) && count($this->post_data['reports'])) {
                $this->post_data['reported'] = count($this->post_data['reports']);
                update_post_meta($this->post_data['ID'], 'peepso_reported',1);
            }
        }

        if($no_html) {
            return $this->post_data;
        }
        if($this->post_data['post_type'] == 'peepso-comment') {
            PeepSoTemplate::exec_template('activity', 'reported-comment-as-post', $this->post_data);
        } elseif('core_reported'== $this->stream_id && PeepSo::is_admin()) {
            PeepSoTemplate::exec_template('activity', 'reported-post', $this->post_data);
        } else {
            PeepSoTemplate::exec_template('activity', 'post', $this->post_data);
        }

        /*
         * https://github.com/peepso/peepso/issues/1536
         * reset wp query to prevent function in another plugin or theme get wrong data
         */
        wp_reset_query();
    }


    /**
     * Ajax callback, returns json encoded data of a page in an activity feed
     * @param  PeepSoAjaxResponse $resp
     */
    public function show_posts_per_page(PeepSoAjaxResponse $resp)
    {
        $start = microtime(TRUE);
        add_filter('peepso_user_profile_id', array(&$this, 'ajax_get_profile_id'));
        $page = $this->_input->int('page', 1);
        $user = $this->_input->int('uid', get_current_user_id());
        $owner = $this->_input->int('user_id');
        $post_id = $this->_input->int('post_id', 0);  // optional, single activity view

        $limit = $this->_input->int('limit', 1); // PeepSo3 search overrides this for search results

        $this->context = $this->_input->value('context','',FALSE); // SQL safe

        // #8003 Stream-filter persistence moved to per-browser localStorage (assets/js/activitystream.js).
        // The server no longer writes user-meta here; it still filters this request from the params the
        // JS sends (stream_id / stream_filter_show_my_posts / stream_filter_sort_by), unchanged below.

        $stream_id_list = apply_filters('peepso_stream_id_list', array());
        $this->stream_id = $this->_input->value('stream_id', PeepSo::get_option('stream_id_default'), array_keys($stream_id_list));

        $this->is_loading_stream = TRUE;



        ob_start();
        do_action('peepso_action_before_pinned_posts', $this);

        $found_posts = 0;
        $post_count = 0;

        $no_html = $this->_input->int('no_html',0);
        $no_html_data = [];

        $improve_query = PeepSo::get_option_new('remove_sql_cal_found_rows_enable');
        if ($improve_query) {
            $found_posts = $page > 1 ? ($page * $limit): 0;
        }

        if(PeepSo::get_option_new('pinned_posts_enable') && 1==$this->_input->int('pinned', 0)) {
            $this->get_posts(NULL, $owner, $user, $page, TRUE, -1, $post_id);
            $found_posts += $improve_query ? $this->post_query->post_count : $this->post_query->found_posts;
            $post_count += $this->post_query->post_count;

            while ($this->next_post()) {

                if($no_html) {
                    $no_html_data[] = $this->show_post(TRUE);
                    continue;
                }

                $this->show_post(); // display post and any comments
            }
        }


        if($this->_input->value('stream_filter_sort_by',FALSE)!='pinned_only') {
            $this->get_posts(NULL, $owner, $user, $page, FALSE, $limit, $post_id);

            if (!is_null($this->post_query->post)) {
                $act_id = $this->post_query->post->act_id;
                $resp->set('act_id', $act_id);
            }

            $found_posts += $improve_query ? $this->post_query->post_count : $this->post_query->found_posts;
            $post_count += $this->post_query->post_count;
        }

        do_action('peepso_action_before_posts_per_page', $this);

        if($this->_input->value('stream_filter_sort_by',FALSE)!='pinned_only') {
            while ($this->next_post()) {
                if ($no_html) {
                    $no_html_data[] = $this->show_post(TRUE);
                    continue;
                }
                $this->show_post(); // display post and any comments
            }
        }

        if($no_html) {
            $resp->set('no_html_data', $no_html_data);
            return;
        }

        if($page == 1) {
            (new PeepSo3_Search_Analytics())->store($this->_input->value('search', NULL, FALSE) , 'activity');
        }


        // Load stream ads if PeepSo installation is older than 90 days
        if(PeepSo3_Helper_Addons::license_is_free_bundle()) {

            $rules = ['start' => 5, 'repeat' => 3, 'delay' => 24 * 21];

            $pscc =  /** checked for NULL**/ (new PeepSoCom_Connect('upsell/peepso-free-bundle/stream-ads-rules.json',TRUE, NULL, TRUE))->get();

            if(NULL === $pscc) {
                // do nothing, will use fallback rules
            } elseif(is_object($pscc)) {
                $pscc = (array) $pscc;
                $rules = array_merge($rules, $pscc);
            }

            if (PeepSo3_Helper_Addons::get_peepso_age() > $rules['delay']) {
                $show = FALSE;
                if ($post_id) {
                    $show = TRUE;
                }
                if ($rules['start'] == $page) {
                    $show = TRUE;
                }
                if ($rules['repeat'] > 0 && 0 == ($page - $rules['start']) % $rules['repeat']) {
                    $show = TRUE;
                }

                if ($show) {
                    $args = ['page' => $page, 'post_id' => $post_id, 'start' => $rules['start'], 'repeat' => $rules['repeat']];
                    $url  = add_query_arg($args, 'upsell/peepso-free-bundle/stream-ads-content.php');
                    $pscc =  /** checked for NULL**/ (new PeepSoCom_Connect($url))->get();

                    if(NULL === $pscc) {
                        echo '';
                    } else {
                        echo $pscc;
                    }
                }
            }
        }

        $max_num_pages = $improve_query ? ceil($found_posts / $limit) : $this->post_query->max_num_pages;

        $this->show_more_posts_link();
        $resp->set('render_time', microtime(TRUE)-$start);
        $resp->set('max_num_pages', $max_num_pages);
        $resp->set('found_posts', $found_posts);
        $resp->set('post_count', $post_count);
        $resp->set('posts', ob_get_clean());
    }

    public function set_human_friendly(PeepSoAjaxResponse $resp) {

        if($this->_input->int('reset',0)) {
            delete_post_meta_by_key('peepso_human_friendly');
            die('RESET');
        }

        $post_id = $this->_input->int('post_id');
        $user_id = get_current_user_id();
        $human_friendly = str_replace("\n", " ", urldecode($this->_input->value('human_friendly','',FALSE))); // SQL safe, meta

        if(!strlen($human_friendly)) {
            $resp->success(FALSE);
            $resp->error('TEXT_INVALID');
            return;
        }

        if($post = get_post($post_id)) {

            if (!PeepSo::is_admin() && $post->post_author != $user_id) {
                $resp->error('USER_INVALID');
                return;
            }

            update_post_meta($post_id, 'peepso_human_friendly', $human_friendly);
            $resp->success(TRUE);
            return;
        }

        $resp->success(FALSE);
        $resp->error('POST_NOT_FOUND');
    }

    public function stream_status(PeepSoAjaxResponse $resp)
    {
        add_filter('peepso_user_profile_id', array(&$this, 'ajax_get_profile_id'));

        $user = $this->_input->int('uid');
        $owner = $this->_input->int('user_id');
        $this->stream_id = $this->_input->int('stream_id', 1);

        $user = $this->_input->int('uid');
        $owner = $this->_input->int('user_id');
        $this->stream_id = $this->_input->int('stream_id', 1);



        $this->get_posts(NULL, $owner, $user, 0, FALSE, 1);

        $all_posts = $this->_input->int('all_posts');
        $all_posts_new = intval($this->post_query->found_posts);

        $new_posts = $all_posts - $all_posts_new;
        $new_posts = ($new_posts<0) ? 0 : $new_posts;


        $resp->set('new_posts', $new_posts);

        $resp->success(TRUE);
    }


    /**
     * Filter callback to retrieve the current profile id, used in filter_post_clauses.
     * @return int The current viewed profile ID
     */
    public function ajax_get_profile_id()
    {
        return ($this->_input->int('user_id'));
    }


    /*
     * return a WP_Query instance for all custom post types by peepso
     * @param string $orderby The column which to sort from
     * @param string $order Sort direction
     * @param int $limit The number of posts to limit the query by
     * @param int $offset The number of posts to offset the query by
     * @param array $search Key-value pair of search options using sub parameters of WP_Query
     * @return WP_Query instance of queried Post data
     */
    public function get_all_activities($orderby = 'post_date_gmt', $order = 'DESC', $limit = NULL, $offset = NULL, $search = array())
    {
        $args = array(
            'post_type' => apply_filters('peepso_activity_post_types',
                array(
                    PeepSoActivityStream::CPT_POST,
                    #PeepSoActivityStream::CPT_COMMENT
                )
            ),
            'order_by' => $orderby,
            'order' => $order,
            'post_status' => 'any',
            'posts_per_page' => (NULL === $limit ? -1 : $limit),
            'offset' => (NULL === $offset ? 0 : $offset)
        );

        foreach ($search as $parameter => $value)
            $args[$parameter] = $value;

        return (new WP_Query($args));
    }


    /*
     * Callback for filtering post content to create anchor links
     * @param string $content The post content to filter
     * @return string Modified post content with URL converted to <a>nchor links
     */
    public function activity_post_content($content)
    {
        $cont = trim($content);		// empty(trim()) throws a PHP error
        if (empty($cont) && (strlen($cont) == 0))
            return ('');

        // #3766 encode stray "<" (user text that isn't a valid tag) so it survives kses/strip_tags/the
        // browser, while preserving genuine tags and the entity decode the URL linkifier relies on.
        $content = PeepSo3_Utilities_String::encode_stray_lt(html_entity_decode($content, ENT_COMPAT, 'UTF-8'));

        $pattern = '/(?<=^|\s)(' .
            // Common TLDs does not need to have a scheme.
            '((https?:\/\/)?([a-z0-9-]+\.)+((com|net|org|int|edu|gov|mil|biz|info|mobi|co|io|me)(\.[a-z]{2})?)(?![a-z]))|' .
            // Other TLDs need to have a scheme to make sure it is a URL.
            '((https?:\/\/)([a-z0-9-]+\.)+([a-z]{2,24}))' .
            ')(:\d+)?(\/[^\s]*)?/i';
        $content = preg_replace_callback($pattern, array(&$this, 'make_link'), $content);

        return $content;
    }

    /*
     * Callback for preg_replace_callback to convert URLs to <a>nchor links
     * OR images/video if applicable
     *
     * @param array $matches The matched items
     * @return string the new content, with <a>nchor links added
     */

    /**
     * Assigns the oemebed type
     * @param  array $return
     * @param  object $data The oembed response data
     * @return array
     */
    public function oembed_dataparse($return, $data)
    {
        $this->post_media['oembed_type'] = $data->type;

        return ($return);
    }

    /**
     * Assigns the oemebed args limit data size
     * @param  array $return
     * @param  object $data The oembed response data
     * @return array
     */
    public function oembed_limit_response_size($args)
    {
        if(isset($args['limit_response_size'])) {
            $args['limit_response_size'] = 2048000; // 2 MB
        }

        return ($args);
    }

    public function make_link($matches)
    {
        $url = strip_tags($matches[0]);
        $url = trim($url);

        $url_text = $url;

        // Automatically add HTTPS by default if no scheme is provided.
        if (FALSE === strpos($url, '://')) {
            $url = 'https://' . $url;
        }
        // prevent SSRF, such as file://, ftp://, gopher://
        else if (strpos($url, '://') !== FALSE && strpos($url, 'https://') === FALSE && strpos($url, 'http://') === FALSE) {
            return $url;
        }

        $target = '';
        $link_target = (int) PeepSo::get_option('site_activity_open_links_in_new_tab',1);
        if (1 === $link_target) {
            $target = 'target="_blank"';
        } else if (2 === $link_target && 0 !== strpos($url, site_url())) {
            $target = 'target="_blank"';
        }

        if (!empty($this->post_media))
            return ('<a class="ps-media__link ps-media-link" href="' . $url . '" rel="nofollow" ' . $target . '>' . $url_text . '</a>');


        $allow_embed = PeepSo::get_option('allow_embed', 1) != 0;

        /**
         * Allow plugins to override the `allow_embed` setting.
         *
         * @since 3.1.0.0
         */
        $allow_embed = apply_filters('peepso_allow_embed', $allow_embed);

        // if embed is disabled
        if (!$allow_embed) {
            return '<a class="ps-media__link ps-media-link" href="' . $url . '" rel="nofollow" ' . $target . '>' . $url_text . '</a>';
        }

        $this->post_media = array(
            'title' => $url,
            'description' => $url,
        );

        // Skip if client send embed data.
        $embed = $this->_input->value('embed', NULL, FALSE);
        if ($embed) {
            return ('<a class="ps-media__link ps-media-link" href="' . $url . '" rel="nofollow" ' . $target . '>' . $url_text . '</a>');
        }

        // Get first image/video

        add_filter('oembed_dataparse', array(&$this, 'oembed_dataparse'), 10, 2);
        add_filter('oembed_remote_get_args', array(&$this, 'oembed_limit_response_size'), 10, 2);
        $embed_code = ps_oembed_get($url, array('width' => 500, 'height' => 300), TRUE);
        remove_filter('oembed_remote_get_args', array(&$this, 'oembed_limit_response_size'));
        remove_filter('oembed_dataparse', array(&$this, 'oembed_dataparse'), 10, 2);

        if (strlen($embed_code['html'])) {

            $this->post_media['content'] = $embed_code['html'];
            $this->post_media['force_oembed'] = $embed_code['force_oembed'];

        } else if ($this->is_image_link($url)) {
            $parts = parse_url($url);

            $this->post_media['content'] = '';
            $this->post_media['title'] = $parts['host'];
            $this->post_media['description'] = $parts['host'];
            $this->post_media['content'] = '<img src="' . $url . '" height="150" width="150" alt="" />';
        }

        $og_tags = $this->_fetch_og_tags($url);

        // has og_tags available
        // overide the content
        // todo: handle for rich content from wordpress that using peepso
        /*if((isset($this->post_media['title']) && !empty($this->post_media['title'])) &&
            (isset($this->post_media['oembed_type']) && $this->post_media['oembed_type'] == 'rich')) {
            unset($this->post_media['oembed_type']);
            unset($this->post_media['content']);
            $this->post_media['force_oembed'] = FALSE;
        }*/

        if (($og_tags) && $og_tags->image) {

            if(!isset($this->post_media['content'])) {
                $this->post_media['content'] = '<img class="ps-media__cover-image" src="' . esc_url($og_tags->image) . '" alt="" />';
            }

            $this->post_media['og_image_url'] = esc_url($og_tags->image);

            $this->post_media['og_image'] = $og_tags->image;
        }


        // generate hash to avoid duplicate media
        if (FALSE === empty($this->post_media)) {
            $this->post_media['url'] = $url;
            $hash = md5(serialize($this->post_media));
            $this->peepso_media[$hash] = $this->post_media;
        }

        return ('<a class="ps-media__link ps-media-link" href="' . $url . '" rel="nofollow" ' . $target . '>' . $url_text . '</a>');
    }

    /**
     * Checks whether the given URL is a link to an image.
     * Requires CURL library.
     *
     * @param  string $url The image URL to be checked.
     * @return boolean
     */
    private function is_image_link($url)
    {
        if ( ! function_exists('curl_version') ) {
            return FALSE;
        }

        $ch = curl_init( trim($url) );
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

        $content = curl_exec($ch);
        $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
        $imageTypes = array('image/gif', 'image/png', 'image/jpeg', 'image/bmp', 'image/webp');

        return in_array( strtolower($contentType), $imageTypes );
    }

    /**
     * Try and get facebook og tags for the given URL, sets post_media values.
     * @param  string $url The URL to get og tags of.
     * @return array The OpenGraph tags available, if any.
     */
    private function _fetch_og_tags($url)
    {
        $og_tags = PeepSoOpenGraph::fetch($url);

        if ($og_tags) {
            // #8110 set_media_properties() has already replaced the $url placeholder
            // with the oEmbed title when the provider supplied one. That is
            // authoritative, so only let a scraped og:title fill in the gap -
            // a remote page serving a degraded shell must not clobber it.
            $title_is_placeholder = !isset($this->post_media['title']) || $url === $this->post_media['title'];

            if ($og_tags->title && $title_is_placeholder)
                $this->post_media['title'] = $og_tags->title;

            if ($og_tags->description)
                $this->post_media['description'] = $og_tags->description;
        }

        return ($og_tags);
    }

    /**
     * Set width and height of the videos
     * @param  string $html The embed HTML
     * @param  string $url  Media URL
     * @param  array $args  An array of arguments passed.
     * @return string The customized HTML.
     */
    public function oembed_result($html, $url, $args)
    {
        // Make video width follows container width.
        $html = preg_replace('#\swidth=["\']\d+%?["\']#i', " width='100%'", $html);

        return $html;
    }

    /**
     * Returns the current retrieved link information
     * @return array
     */
    public function get_media()
    {
        return ($this->post_media);
    }

    /**
     * Set media properties
     * @param  array $return
     * @param  object $data The oembed response data
     */
    public function set_media_properties($return, $data)
    {
        if (isset($data->title))
            $this->post_media['title'] = $data->title;
        return ($return);
    }

    /*
     * Outputs <select> element for list of reporting reasons
     */
    public function report_reasons()
    {
        $reasons = str_replace("\r", '', PeepSo::get_option('site_reporting_types', __('Spam', 'peepso-core')));
        $reasons = explode("\n", $reasons);

        $reasons = apply_filters('peepso_activity_report_reasons', $reasons);

        echo '<select class="ps-input ps-input--sm ps-input--select ps-js-report-type">', PHP_EOL;
        echo '<option value="">', esc_attr__('- select reason -', 'peepso-core'), '</option>', PHP_EOL;
        foreach ($reasons as $reason) {
            $reason = esc_attr($reason);
            $reason = stripslashes($reason);
            echo '<option value="', wp_kses_post($reason), '" data-need-reason="'.esc_attr(apply_filters('peepso_activity_report_reasons_need_reason', 0, $reason)).'">', wp_kses_post($reason), '</option>';
        }
        echo '<option value="Other" data-need-reason="1">' . esc_attr__('Other', 'peepso-core') . '</option>';
        echo '</select>', PHP_EOL;

        echo '<div class="ps-js-report-desc">';
        echo '<textarea class="ps-input ps-input--textarea" maxlength="250" placeholder="' . esc_attr__( 'Report description...', 'peepso-core' ) . '"></textarea>';
        echo '<div class="ps-form__chars-count ps-js-counter"></div>';
        echo '</div>';

        echo '<div class="ps-alert ps-alert--abort ps-js-report-error" style="display:none"></div>';
    }


    /*
     * This template tag gives add-on authors a chance to output dialog box HTML content
     */
    public function dialogs()
    {
        do_action('peepso_activity_dialogs');
    }

    /**
     * Return the number of likes for an activity
     * @param  int  $act_id The act_id to look for
     * @return int  The number of likes
     */
    public function has_likes($act_id)
    {
        $activity = $this->get_activity($act_id);
        $like = PeepSoLike::get_instance();
        return ($like->get_like_count($activity->act_external_id, $activity->act_module_id));
    }

    /**
     * Show the "`n` person likes this" text
     * @param  int $count Set like count display explicitly
     */
    public function show_like_count($count = 0, $act_id = NULL)
    {
        if (NULL === $act_id) {
            global $post;
            $act_id = $post->act_id;
        }

        if ($count > 0)
            echo '<a href="#" onclick="return activity.show_likes(', esc_attr($act_id), ');">',
            esc_attr($count), esc_attr(_n(' person likes this', ' people like this.', $count, 'peepso-core')), '</a>';
    }

    /**
     * Return an html list of persons who liked a post.
     */
    public function get_like_names(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id');

        $activity = $this->get_activity($act_id);

        $like = PeepSoLike::get_instance();
        $names = $like->get_like_names($activity->act_external_id, $activity->act_module_id);

        if (count($names) > 0) {
            $users = array();
            $html_names = array();
            $html = '';

            foreach ($names as $name) {
                $user = PeepSoUser::get_instance($name->ID);
                ob_start();
                do_action('peepso_action_render_user_name_before', $user->get_id());
                $before_fullname = ob_get_clean();

                ob_start();
                do_action('peepso_action_render_user_name_after', $user->get_id());
                $after_fullname = ob_get_clean();

                $users[$name->ID] = array(
                    'display_name' => $user->get_fullname(),
                    'profile_url' => $user->get_profileurl()
                );

                $html_names[] = '<a class="ps-comment-user" href="' . $users[$name->ID]['profile_url']
                    . '" data-hover-card="' . $name->ID . '">'
                    . $users[$name->ID]['display_name'] . '</a>';
            }

            $users = apply_filters('peepso_activity_like_names', $users, $act_id);

            $html .= implode(', ', $html_names);
            $html .= _n(' liked this', ' like this', 1, 'peepso-core');

            $resp->success(TRUE);
            $resp->set('users', $users);
            $resp->set('html', $html);
        } else {
            $resp->success(FALSE);
        }
    }

    public function set_comments_status(PeepSoAjaxResponse $resp) {
        $post_id = $this->_input->int('post_id', 0);

        if(!$this->can_disable_comments($post_id))
        {
            $resp->error(__('You do not have permission to do that', 'peepso-core'));
            $resp->success(FALSE);
            return FALSE;
        }

        $open = $this->_input->int('open', 1);

        if($open) {
            delete_post_meta($post_id, 'peepso_disable_comments');
        } else {
            update_post_meta($post_id, 'peepso_disable_comments', 1);
        }

        $resp->success(TRUE);
    }

    private function can_disable_comments($post_id) {

        // Admin privilege cannot be overriden
        if(PeepSo::is_admin()) {
            return TRUE;
        }

        // Owner privilege cannot be overriden
        $post = get_post($post_id);

        if(PeepSo::get_option_new('post_owner_can_disable_comments') && $post->post_author == get_current_user_id()) {
            return TRUE;
        }

        // Other plugins can allow comment closing under some circumstances
        return apply_filters('peepso_can_disable_comments', FALSE, $post_id);
    }

    public function pin(PeepSoAjaxResponse $resp)
    {
        $pin_status = $this->_input->int('pinstatus',0);
        $pin_until  = $this->_input->value('pinuntil',FALSE);
        $post_id    = $this->_input->int('postid',0);

        $this->get_post($post_id);
        $post = get_post($post_id);
        $post->can_be_pinned = 1;
        $post = apply_filters('peepso_post_can_be_pinned', $post);

        // BREAK for unknown action
        if( !in_array($pin_status, array(0,1)) || $post->can_be_pinned == 0) {
            $resp->error(__('Invalid Action', 'peepso-core'));
            $resp->success(FALSE);
            return( FALSE );
        }

        // BREAK If post not found
        if(!$this->has_posts()) {
            $resp->error(__('Invalid Post ID', 'peepso-core'));
            $resp->success(FALSE);
            return( FALSE );
        }

        // BREAK If not admin
        if (!PeepSo::can_pin($post->ID)) {
            $resp->error(__('You do not have permission to do that', 'peepso-core'));
            $resp->success(FALSE);
            return( FALSE );
        }

        $this->next_post();

        delete_post_meta($post_id, 'peepso_pinned');
        delete_post_meta($post_id, 'peepso_pinned_by');
        delete_post_meta($post_id, 'peepso_pinned_until');

        if( 1 == $pin_status ) {
            add_post_meta($post_id, 'peepso_pinned', current_time('timestamp', TRUE), TRUE);
            add_post_meta($post_id, 'peepso_pinned_by', get_current_user_id(), TRUE);

            if($pin_until) {
                $pin_until = strtotime($pin_until);

                // Using GMT offset should set this to server time?
                $pin_until = $pin_until - 3600 * PeepSoUser::get_gmt_offset(get_current_user_id());
                add_post_meta($post_id, 'peepso_pinned_until', $pin_until, TRUE);
            }
        }

        $resp->success(TRUE);
        return TRUE;
    }

    public function nsfw(PeepSoAjaxResponse $resp)
    {
        $nsfw_status = $this->_input->int('nsfw_status',0);
        $post_id 	= $this->_input->int('postid',0);

        $this->get_post($post_id);
        $post = get_post($post_id);

        // BREAK for unknown action
        if( !in_array($nsfw_status, array(0,1))) {
            $resp->error(__('Invalid Action', 'peepso-core'));
            $resp->success(FALSE);
            return( FALSE );
        }

        // BREAK If post not found
        if(!$this->has_posts()) {
            $resp->error(__('Invalid Post ID', 'peepso-core'));
            $resp->success(FALSE);
            return( FALSE );
        }

        // BREAK If not admin or author
        if (!PeepSo::can_nsfw($post->ID)) {
            $resp->error(__('You do not have permission to do that', 'peepso-core'));
            $resp->success(FALSE);
            return( FALSE );
        }

        $this->next_post();

        delete_post_meta($post_id, 'peepso_nsfw');
        delete_post_meta($post_id, 'peepso_nsfw_by');

        if( 1 == $nsfw_status ) {
            add_post_meta($post_id, 'peepso_nsfw', current_time('timestamp', TRUE), TRUE);
            add_post_meta($post_id, 'peepso_nsfw_by', get_current_user_id(), TRUE);
        }

        $resp->success(TRUE);
        return TRUE;
    }

    public function add_view_count(PeepSoAjaxResponse $resp) {
        $resp->success(PeepSoActivityRanking::add_unique_view_count($this->_input->int('act_id')));
    }


    /**
     * Return an array of Like information. Icon, count and label to display.
     * @param  int $post_id The post ID of which item to get like info from
     * @return array The Like data
     */
    public function get_like_status($post_id, $module_id = PeepSoActivity::MODULE_ID)
    {
        $logged_in = is_user_logged_in();
        $like = PeepSoLike::get_instance();
        $likes = $like->get_like_count($post_id, $module_id);
        $user_liked = $like->user_liked($post_id, $module_id, get_current_user_id());

        $like_icon = 'gcir gci-thumbs-up';
        $like_label = $like_text = __('Like', 'peepso-core');

        if ($likes > 0 && $logged_in) {
            $like_label = '<span title="' . $likes . _n(' person likes this', ' people like this', $likes, 'peepso-core') .
                '">' . $like_text . '</span>';
        }

        return (array(
            'label' => $like_label,
            'icon' => $like_icon,
            'count' => $likes,
            'liked' => $user_liked
        ));
    }

    /**
     * Get a single post's html via ajax.
     * @param  PeepSoAjaxResponse $resp
     */
    public function ajax_show_post(PeepSoAjaxResponse $resp)
    {
        global $post;

        $owner_id = $this->_input->int('user_id');
        $user_id = $this->_input->int('uid');
        $act_id = $this->_input->int('act_id');

        $activity = $this->get_activity($act_id);

        $act_post = apply_filters('peepso_activity_get_post', NULL, $activity, $owner_id, $user_id);
        if (NULL !== $act_post) {
            $post = get_post($act_post->ID, OBJECT);
            setup_postdata($act_post);

            ob_start();
            $this->content();
            $this->post_attachment();

            $resp->set('html', ob_get_clean());
            $resp->success(TRUE);
        } else
            $resp->success(FALSE);
    }

    /**
     * Returns the HTML content to display or NULL if no relevant content is found
     * @param  string $post  The post to return
     * @param  array $activity  The activity data
     * @param  int $owner_id The owner of the activity
     * @param  int $user_id The user requesting access to the activity post
     *
     * @return mixed The HTML post to display | NULL if no relevant post is found
     */
    public function activity_get_post($post, $activity, $owner_id, $user_id)
    {
        if (NULL === $post && is_object($activity)) {
            $this->get_post($activity->act_external_id, $owner_id, $user_id, TRUE);

            if ($this->post_query->have_posts()) {
                $this->post_query->the_post();
                $post = $this->post_query->post;
            }
        }

        return ($post);
    }

    /**
     * Get a single comment's html via ajax.
     * @param  PeepSoAjaxResponse $resp
     */
    public function ajax_show_comment(PeepSoAjaxResponse $resp)
    {
        global $post;

        $act_id = $this->_input->int('act_id');
        $activity = $this->get_activity($act_id);

        $this->get_comment($activity->act_external_id);
        $this->comment_query->the_post();

        ob_start();
        $this->content();
        $this->comment_attachment();
        $resp->set('html', ob_get_clean());
        $resp->set('act_id', $post->act_id);

        $resp->success(TRUE);
    }

    /**
     * Return a WP_Query object containing comments from the given post, grouped by author.
     * @param  int $post_id The post to get the comments from.
     * @return object WP_Query object
     */
    public function get_comment_users($post_id, $module_id)
    {
        $args = array(
            'post_type' => $this->query_type = PeepSoActivityStream::CPT_COMMENT,
            '_comment_object_id' => $post_id,
            '_comment_module_id' => $module_id
        );

        add_filter('posts_groupby', array(&$this, 'groupby_author_id'), 10, 1);
        add_filter('posts_join', array(&$this, 'filter_act_join'));
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10, 2);
        add_filter('posts_clauses_request', array(&$this, 'filter_post_clauses_comments'), 20, 2);
        $this->is_comment_query = TRUE;
        $query = new WP_Query($args);
        remove_filter('posts_groupby', array(&$this, 'groupby_author_id'), 10);
        remove_filter('posts_join', array(&$this, 'filter_act_join'));
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses'), 10);
        remove_filter('posts_clauses_request', array(&$this, 'filter_post_clauses_comments'), 20);
        $this->is_comment_query = FALSE;

        return ($query);
    }

    /**
     * posts_groupby callback to group by author.
     * @param  string $groupby The groupby string.
     * @return string
     */
    public function groupby_author_id($groupby)
    {
        global $wpdb;

        return ($wpdb->posts . ".post_author");
    }

    /**
     * Change a post's privacy setting.
     * @param  PeepSoAjaxResponse $resp
     */
    public function change_post_privacy(PeepSoAjaxResponse $resp)
    {
        $act_id = $this->_input->int('act_id');
        $user_id = $this->_input->int('uid');

        $activity = $this->get_activity_post($act_id);
        $owner_id = intval($activity->post_author);

        // SQL safe, WP sanitizes it
        if (wp_verify_nonce($this->_input->value('_wpnonce','',FALSE), 'peepso-nonce') &&
            PeepSo::check_permissions($owner_id, PeepSo::PERM_POST_EDIT, $user_id)) {
            global $wpdb;

            $aActData = array(
                'act_access' => $this->_input->int('acc'),
            );

            do_action('peepso_activity_change_privacy', $activity, $this->_input->int('acc'));

            $resp->notice(__('Changes saved.', 'peepso-core'));
            $resp->success($wpdb->update($wpdb->prefix . self::TABLE_NAME, $aActData, array('act_id' => $act_id)));
        } else {
            $resp->error(__('You do not have permission to change post privacy settings.', 'peepso-core'));
            $resp->success(FALSE);
        }
    }

    public function postbox_author_avatar($PeepSoUser)
    {
        $avatar = $PeepSoUser->get_avatar();
        $anon_id = class_exists('PeepSo3_Anon') ? PeepSo3_Anon::get_instance()->anon_id : 0;
        $avatar_anon = $anon_id
            ? PeepSoUser::get_instance($anon_id)->get_avatar()
            : PeepSo::get_asset('images/avatar/user-neutral.png');
        $output = '<a href="'.$PeepSoUser->get_profileurl().'"><img class="pso-avatar__image" src="'.$avatar.'" src-orig="'.$avatar.'" src-anon="'.$avatar_anon.'" alt="'.$PeepSoUser->get_fullname() .'"></a>';
        echo apply_filters('peepso_postbox_author_avatar', $output);
    }

    public function postbox_author_fullname($PeepSoUser)
    {
        $name = $PeepSoUser->get_fullname();
        $name_anon = __('Anonymous User', 'peepso-core');
        $output = '<span><span data-name-orig="'.esc_attr($name).'" data-name-anon="'.esc_attr($name_anon).'">' . $name . '</span> <span data-ps="title-extra" style="font-weight:normal"></span></span>';
        echo apply_filters('peepso_postbox_author_fullname', $output);
    }

    /**
     * Outputs the author of an activity stream item
     */
    public function post_action_author_avatar($post_id, $hide_post_header, $post_author, $PeepSoUser)
    {
        $output = '<a ' . $hide_post_header . ' class="ps-avatar ps-avatar--post" href="' . $PeepSoUser->get_profileurl() . '" data-hover-card="' . intval($post_author) . '"><img data-author="' . $post_author . '" src="'. $PeepSoUser->get_avatar().'" alt="' .$PeepSoUser->get_fullname().' avatar" /></a>';
        echo apply_filters('peepso_activity_stream_author_avatar', $output, $post_id, $hide_post_header, $post_author, $PeepSoUser);
    }

    /**
     * Outputs the comments author of an activity stream item
     */
    public function post_action_comments_author_avatar($post_id, $post_author, $PeepSoUser)
    {
        $output = '<a href="' . $PeepSoUser->get_profileurl() . '" data-hover-card="' . intval($post_author) . '"><img data-author="' . $post_author . '" src="'. $PeepSoUser->get_avatar().'" alt="' .$PeepSoUser->get_fullname().' avatar" /></a>';
        echo apply_filters('peepso_activity_stream_comments_author_avatar', $output, $post_id, $post_author, $PeepSoUser);
    }

    public function post_action_comments_author_name($post_id, $post_author, $PeepSoUser)
    {
        $output = '<div class="ps-comment__author">@peepso_user_'.$post_author.'('. $PeepSoUser->get_fullname().')</div>';
        echo apply_filters('peepso_activity_stream_comments_author_name', $output, $post_id, $post_author, $PeepSoUser);
    }

    public function post_commentbox_author_avatar($post_id, $post_author, $PeepSoUser)
    {
        $output = '<a class="ps-avatar cstream-avatar cstream-author" href="' . $PeepSoUser->get_profileurl() . '" data-hover-card="' . $PeepSoUser->get_id() . '"><img data-author="' . $PeepSoUser->get_id() . '" src="'. $PeepSoUser->get_avatar() . '" alt="" /></a>';
        echo apply_filters('peepso_activity_commentsbox_author_avatar', $output, $post_id, $post_author, $PeepSoUser);
    }

    /**
     * Outputs the description of an activity stream item
     */
    public function post_action_title($post_id = NULL)
    {
        global $post;

        $action = '';
        if ($post->post_author === $post->act_owner_id) {
            $post_id = $post_id != NULL ? $post_id : $post->ID;
            $user = PeepSoUser::get_instance($post->post_author);

            // check anonymous
            if (PeepSo::get_option_new('postbox_anon_enabled')) {
                $anon_op = get_post_meta($post_id, PeepSo3_anon::META_POST_ANON_OP, TRUE);
                if(PeepSo3_Utilities_String::maybe_strlen($anon_op)) {
                    $user = PeepSoUser::get_instance(PeepSo3_Anon::get_instance()->anon_id);
                }
            }

            $default_action = __('shared a %s', 'peepso-core');
            $default_action_text = __('post', 'peepso-core');

            $default_action = '%s';
            $default_action_text = '';

            if ($post->act_repost_id) {
                $repost = $this->get_activity_post($post->act_repost_id);

                if (NULL !== $repost)
                    $default_action_text = '<a href="' . PeepSo::get_page('activity_status') . $repost->post_title . '/' . '">' . $default_action_text . '</a>';
            }
            ob_start();
            do_action('peepso_action_render_user_name_before', $user->get_id());
            $before_fullname = ob_get_clean();

            ob_start();
            do_action('peepso_action_render_user_name_after', $user->get_id());
            $after_fullname = ob_get_clean();

            $mention = apply_filters('peepso_activity_stream_mention', '@peepso_user_'.$user->get_id().'('.$user->get_fullname().')');

            $action = apply_filters('peepso_activity_stream_action', sprintf($default_action, $default_action_text), $post);
            $title = sprintf('%s <span class="ps-post__subtitle ps-stream-action-title">%s</span> ',
                $mention, $action);
        } else {
            $author = PeepSoUser::get_instance($post->post_author);
            $owner = PeepSoUser::get_instance($post->act_owner_id);

            ob_start();
            do_action('peepso_action_render_user_name_before', $author->get_id());
            $before_authorname = ob_get_clean();

            ob_start();
            do_action('peepso_action_render_user_name_after', $author->get_id());
            $after_authorname = ob_get_clean();

            ob_start();
            do_action('peepso_action_render_user_name_before', $owner->get_id());
            $before_ownername = ob_get_clean();

            ob_start();
            do_action('peepso_action_render_user_name_after', $owner->get_id());
            $after_ownername = ob_get_clean();

            $title = sprintf(
                '<a class="ps-post__author" href="%s" data-hover-card="%d">%s</a><i class="gcis gci-angle-right"></i>'
                .'<a class="ps-avatar" href="%s" data-hover-card="%d"><img src="%s"/></a>'
                .' &nbsp; '
                .'<a class="ps-post__subtitle" href="%s" data-hover-card="%d">%s</a>',

                $author->get_profileurl(), $author->get_id(), $before_authorname . $author->get_fullname() . $after_authorname,
                $author->get_profileurl(), $owner->get_id(), $owner->get_avatar(),
                $owner->get_profileurl(), $owner->get_id(), $before_ownername . $owner->get_fullname() . $after_ownername
            );
        }

        echo apply_filters('peepso_activity_stream_title', $title, $post, $action);
    }

    /**
     * Returns the act_id of the original activity being shared.
     *
     * @param  int $post_id
     * @return int
     */
    protected function get_repost_root($post_id)
    {
        if (empty($post_id))
            return (0);

        $sql = $this->_get_repost_root_query($post_id);
        $sql .= ' LIMIT 1';

        global $wpdb;
        $result = $wpdb->get_row($sql);

        return ($result->act_id);
    }

    /**
     * Returns the sql string to generate the hierarchy of repost events of an activity.
     *
     * source: http://explainextended.com/2009/07/20/hierarchical-data-in-mysql-parents-and-children-in-one-query/
     *
     * @param  intval $post_id The post ID
     * @return string The prepared sql query
     */
    private function _get_repost_root_query($post_id)
    {
        global $wpdb;

        $sql = "SELECT `T2`.`act_id`
			FROM (
				SELECT
					@r AS `_id`,
					(SELECT @r := `act_repost_id` FROM `{$wpdb->prefix}" . self::TABLE_NAME . "` WHERE `act_id` = _id) AS `act_repost_id`,
					@l := @l + 1 AS lvl
				FROM
					(SELECT @r := %d, @l := 0) vars,
					`{$wpdb->prefix}" . self::TABLE_NAME . "` `h`
				) `T1`
			JOIN `{$wpdb->prefix}" . self::TABLE_NAME . "` `T2`
				ON `T1`.`_id` = `T2`.`act_id`
			ORDER BY `T1`.`lvl` DESC";

        return ($wpdb->prepare($sql, $post_id));
    }

    /**
     * Removes the Only Me access if a post does not belong to the current user's stream
     * @param  array $acc
     * @return array
     */
    public function privacy_access_levels($acc)
    {
        global $post;

        if(!$post instanceof WP_Post) {
            return $acc;
        }

        if('page' == $post->post_type || 'post' == $post->post_type) {
            return $acc;
        }

        if($post->post_author !== $post->act_owner_id) {
            unset($acc[PeepSo::ACCESS_PRIVATE]);
        }

        return ($acc);
    }

    /**
     * Saves/updates post meta data "peepso_media"
     * @param int $post_id Post content ID
     */
    protected function save_peepso_media($post_id)
    {
        // delete oldies
        delete_post_meta($post_id, 'peepso_media');

        foreach ($this->peepso_media as $post_media) {
            add_post_meta($post_id, 'peepso_media', $post_media);
        }
    }

    /*
     * Output addons for the commentsbox
     * @param int $post_id Post content ID
     */
    public function show_commentsbox_addons($post_id = FALSE)
    {
        // if theres any comments addons at core plugins, need to be added here
        $acts = array();
        $html = array();

        $acts = apply_filters('peepso_commentsbox_interactions', $acts, $post_id);
        $html = apply_filters('peepso_commentsbox_addons', $html, $post_id);

        if (0 === count($acts)) {
            // if no addons, exit
            return;
        }

        echo '<div class="ps-comments__input-addons ps-js-addons">', PHP_EOL;
        echo implode('', $html);
        echo '</div>', PHP_EOL;

        echo '<div class="ps-comments__input-actions ps-js-actions">', PHP_EOL;
        $this->_display_commentsbox_addons($acts);
        echo '</div>', PHP_EOL;
    }


    /**
     * Echo the html for activity feed actions.
     * @param  array $acts  The list of actions with labels and click methods.
     */
    private function _display_commentsbox_addons($acts)
    {
        $logged_in = is_user_logged_in();       // we're using this a lot, save function overhead
        foreach ($acts as $name => $act) {
            echo '<a ';
            if (isset($act['click']) && $logged_in)
                echo ' onclick="', esc_js($act['click']), '" ';
            else
                echo ' onclick="return false;" ';
            if (isset($act['title']) && $logged_in)
                echo ' title="', esc_attr($act['title']), '" ';
            else if (!$logged_in)
                echo ' title="', esc_attr__('Please register or log in to perform this action', 'peepso-core'), '" ';
            echo ' href="', esc_url($logged_in && isset($act['href']) ? $act['href'] : '#'), '" ';
            echo ' class="', (isset($act['class']) ? esc_attr($act['class']) : ''), ' ', esc_attr($act['icon']), '">';
            if (isset($act['label'])) {
                echo '<span>',wp_kses_post($act['label']),'</span>';
            }
            echo '</a>', PHP_EOL;
        }
    }

    /**
     * Handle new embed data.
     * @param int $post_id
     * @param int $act_id
     */
    public function handle_embed_data($post_id = 0, $act_id = 0)
    {
        if (count($this->peepso_media) > 0) {
            foreach ($this->peepso_media as $key => $value) {
                if (isset($value['url'])) {
                    // Get embed data from a URL.
                    $embed = apply_filters('peepso_embed_content', array(), $value['url']);
                    if ($embed['data']) {
                        $embed = array('embed' => $embed['data']);

                        $this->peepso_media[$key] = $embed;
                    }
                }
            }
        } else {
            $type = $this->_input->value('type', '', FALSE);
            if ($type !== 'activity') {
                return;
            }

            $embed_url = $this->_input->raw('embed', NULL, FALSE);
            if (!$embed_url) {
                return;
            }

            // Get embed data from a URL.
            $embed = apply_filters('peepso_embed_content', array(), $embed_url);
            if ($embed['data']) {
                // Use a new key to separate from the previous media data.
                $embed = array('embed' => $embed['data']);

                $hash = md5(serialize($embed));
                $this->peepso_media[$hash] = $embed;
            }
        }
    }

    /**
     * Get the comments var
     */
    public function get_comments_var($post_or_post_id)
    {
        $default = array(
            'comments_batch' => intval(PeepSo::get_option('activity_comments_batch')),
            'comments_to_display' => intval(PeepSo::get_option('site_activity_comments')),
        );

        // prevent warning from filter_before_id
        if (isset($_POST['content'])) {
            return $default;
        }

        $pinned = FALSE;

        if (is_numeric($post_or_post_id)) {
            $post = get_post($post_or_post_id);
        } else {
            $post = $post_or_post_id;
        }

        $pinned_enabled = (PeepSo::get_option_new('pinned_posts_enable'));
        if ($post->post_type == PeepSoActivityStream::CPT_POST && ($pinned_enabled && get_post_meta($post->ID, 'peepso_pinned', TRUE))) {
            $pinned = TRUE;
        } else {
            $activity = $this->get_activity_data($post->ID);
            if (isset($activity->act_comment_object_id)) {
                $root_post = get_post($activity->act_comment_object_id);

                if ($root_post->post_type == PeepSoActivityStream::CPT_COMMENT) {
                    $activity = $this->get_activity_data($root_post->ID);
                    $root_post = get_post($activity->act_comment_object_id);
                }
            }

            if (isset($root_post) && $root_post && ($pinned_enabled && get_post_meta($root_post->ID, 'peepso_pinned', TRUE))) {
                $pinned = TRUE;
            }
        }

        if ($pinned) {
            return array(
                'comments_batch' => intval(PeepSo::get_option('activity_comments_pinned_post_batch', 5)),
                'comments_to_display' => intval(PeepSo::get_option('site_activity_pinned_post_comments', 2)),
            );
        } else {
            return $default;
        }
    }

    /**
     * todo:docblock
     */
    public function get_activity_by_permalink($permalink) {
        global $wpdb;

        $sql = "SELECT `{$wpdb->posts}`.*, `{$wpdb->prefix}" . PeepSoActivity::TABLE_NAME . '`.* ' .
            " FROM `{$wpdb->posts}` " .
            " LEFT JOIN `{$wpdb->prefix}" . PeepSoActivity::TABLE_NAME . "` ON `act_external_id`=`{$wpdb->posts}`.`ID` " .
            ' WHERE `post_name`=%s AND `post_type`=%s ' .
            ' LIMIT 1 ';

        return $wpdb->get_row($wpdb->prepare($sql, $permalink, PeepSoActivityStream::CPT_POST));
    }

    public static function is_permalink_ajax($return_post_id = FALSE) {
        $PeepSoInput = new PeepSoInput();
        $post_id = $PeepSoInput->int('post_id', 0);

        if($return_post_id) {
            return $post_id;
        }

        return ($post_id > 0);
    }

    private function recalculate_last_update($deleted_post_id, $act_data) {
        global $wpdb;

        // Check if act_data is still comment
        if ((int) $act_data->act_comment_object_id !== 0) {
            $act_data = $this->get_activity_data($act_data->act_comment_object_id, $act_data->act_comment_module_id);
        }

        $all_comments = array();

        // Update the post date
        $post_id = $act_data->act_external_id;

        // find all comments and replies of the specified post id
        $currpost = $this->get_post($post_id);
        if ($currpost->have_posts()) {
            $currpost->the_post();
            $this->post_data = get_object_vars($currpost->post);

            if ($this->has_comments()) {
                while ($this->next_comment()) {
                    $post_comment_id = $this->comment_data['ID'];
                    if ($post_comment_id == $deleted_post_id) {
                        continue;
                    }

                    $all_comments[] = $post_comment_id;

                    // Fetch replies
                    $table_name = $wpdb->prefix . PeepSoActivity::TABLE_NAME;
                    $sql = "SELECT `act_external_id`
                        FROM `{$table_name}`
                        WHERE `act_comment_object_id`=%d AND `act_module_id`=%s";
                    $query = $wpdb->prepare($sql, $post_comment_id, PeepSoActivity::MODULE_ID);
                    $all_replies = $wpdb->get_results($query, ARRAY_A);
                    foreach ($all_replies as $reply) {
                        if ($reply['act_external_id'] != $deleted_post_id) {
                            $all_comments[] = $reply['act_external_id'];
                        }
                    }
                }
            }
        }

        $last_comment_post = get_post($post_id);
        $post_modified = $last_comment_post->post_date;
        $post_modified_gmt = $last_comment_post->post_date_gmt;
        if (!empty($all_comments)) {
            // sort an array
            rsort($all_comments);
            $last_comment_post_id = $all_comments[0];
            $last_comment_post = get_post($last_comment_post_id);
            $post_modified = $last_comment_post->post_modified;
            $post_modified_gmt = $last_comment_post->post_modified_gmt;
        }
        // update to the latest post date
        $wpdb->update($wpdb->posts, [
            'post_modified'     => $post_modified,
            'post_modified_gmt' => $post_modified_gmt
        ], ['ID' => $post_id]);
    }

}

// EOF
