Last commit for src/controllers/components/SocialComponent.php: a003aabfc4025180a272e89029be4055134ed367

editing a description of a media detail for when in list or grid mode stays on iframe

Chris Pollett [2024-04-12 17:Apr:th]
editing a description of a media detail for when in list or grid mode stays on iframe
<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2023  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2023
 * @filesource
 */
namespace seekquarry\yioop\controllers\components;

use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library as L;
use seekquarry\yioop\library\CrawlConstants;
use seekquarry\yioop\library\MailServer;
use seekquarry\yioop\library\UrlParser;
use seekquarry\yioop\library\WikiParser;
use seekquarry\yioop\library\FetchUrl;
use seekquarry\yioop\library\PhraseParser;
/**
 * Provides activities to AdminController related to creating, updating
 * blogs (and blog entries), static web pages, and crawl mixes.
 *
 * @author Chris Pollett
 */
class SocialComponent extends Component implements CrawlConstants
{
    /**
     *  Constant for when attempt to handle file uploads and no files were
     *  uploaded
     */
    const UPLOAD_NO_FILES = -1;
    /**
     *  Constant for when attempt to handle file uploads and not all of the
     *  file upload information was present
     */
    const UPLOAD_FAILED = 0;
    /**
     *  Constant for when attempt to handle file uploads and file were
     *  successfully uploaded
     */
    const UPLOAD_SUCCESS = 1;
    /**
     * File to tell RecommendationJob the paths of eligible wiki resources
     * description files
     */
    const RECOMMENDATION_FILE = C\APP_DIR . "/resources/recommendation.txt";
    /**
     * Used to handle the manage group activity.
     *
     * This activity allows new groups to be created out of a set of users.
     * It allows admin rights for the group to be transferred and it allows
     * roles to be added to a group. One can also delete groups and roles from
     * groups.
     *
     * @return array $data information about groups in the system
     */
    public function manageGroups()
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $possible_arguments = ["activateuser",
            "addgroup", "banuser", "creategroup", "deletegroup",
            "deleteselected", "deleteuser", "editgroup", "graphstats",
            "import", "infogroup", "inviteusers", "joingroup",
            "memberaccess", "postlifetime", "registertype", "reinstateuser",
            "search", "statistics", "unsubscribe", "voteaccess"];
        $data["ELEMENT"] = "managegroups";
        $data['SCRIPT'] = "";
        $data['MEMBERSHIP_CODES'] = [
            C\INACTIVE_STATUS => tl('social_component_request_join'),
            C\INVITED_STATUS => tl('social_component_invited'),
            C\ACTIVE_STATUS => tl('social_component_active_status'),
            C\SUSPENDED_STATUS => tl('social_component_suspended_status')
        ];
        $data['REGISTER_CODES'] = [
            C\INVITE_ONLY_JOIN => tl('social_component_invite_only_join'),
            C\REQUEST_JOIN => tl('social_component_unlisted_by_request'),
            C\PUBLIC_BROWSE_REQUEST_JOIN =>
                tl('social_component_by_request'),
            C\PUBLIC_JOIN => tl('social_component_public_join'),
        ];
        if (in_array(C\MONETIZATION_TYPE,
            ['group_fees', 'fees_and_keywords'])) {
            $data['can_monetise_group'] = true;
            $monetise_codes = [100 => tl('social_component_hundred_credits'),
                200 => tl('social_component_two_hundred_credits'),
                500 => tl('social_component_five_hundred_credits'),
                1000 => tl('social_component_thousand_credits'),
                2000 => tl('social_component_two_thousand_credits')];
            $data['REGISTER_CODES'] += $monetise_codes;
        } else {
            $data['can_monetise_group'] = false;
        }
        $data['ACCESS_CODES'] = [
            C\GROUP_READ => tl('social_component_members_only'),
            C\GROUP_READ_COMMENT => tl('social_component_members_can_comment'),
            C\GROUP_READ_WRITE => tl('social_component_members_start_threads'),
            C\GROUP_READ_WIKI => tl('social_component_members_full_access'),
        ];
        $data['VOTING_CODES'] = [
            C\NON_VOTING_GROUP => tl('social_component_no_voting'),
            C\UP_VOTING_GROUP => tl('social_component_up_voting'),
            C\UP_DOWN_VOTING_GROUP => tl('social_component_up_down_voting')
        ];
        $data['POST_LIFETIMES'] = [
            C\FOREVER => tl('social_component_forever'),
            C\ONE_HOUR => tl('social_component_one_hour'),
            C\ONE_DAY => tl('social_component_one_day'),
            C\ONE_MONTH => tl('social_component_one_month'),
        ];
        $data['ENCRYPTION_CODES'] = [
            1 => tl('social_component_encryption_enable'),
            0 => tl('social_component_encryption_disable'),
        ];
        if (in_array(C\MONETIZATION_TYPE, ['group_fees','fees_and_keywords'])) {
            $data['can_monetise_group'] = true;
        } else {
            $data['can_monetise_group'] = false;
        }
        $search_array = [];
        $default_group = ["name" => "", "id" => "", "owner" =>"",
            "register" => -1, "member_access" => -1, 'vote_access' => -1,
            "post_lifetime" => -1, "encryption" => 0];
        $data['CURRENT_GROUP'] = $default_group;
        $data['PAGING'] = "";
        $name = "";
        $data['visible_users'] = "";
        $is_owner = false;
        if (!isset($_REQUEST['arg'])) {
            $_REQUEST['arg'] = "";
        }
        /* start owner verify code / get current group
           $group_id is only set in this block (except creategroup) and it
           is only not null if $group['OWNER_ID'] == $_SESSION['USER_ID'] where
           this is also the only place group loaded using $group_id
        */
        if (!empty($_REQUEST['group_id'])) {
            $group_id = $parent->clean($_REQUEST['group_id'], "int" );
            $group = $group_model->getGroupById($group_id,
                $_SESSION['USER_ID']);
            $info_no_access = false;
            if (empty($group) && ($_REQUEST['arg'] ?? "") == 'infogroup') {
                $group = $group_model->getGroupById($group_id,
                    C\ROOT_ID);
                $info_no_access = true;
            }
            if (isset($group['OWNER_ID'] ) &&
                ($group['OWNER_ID'] == $_SESSION['USER_ID'] ||
                (isset($_REQUEST['arg']) &&
                ($_SESSION['USER_ID'] == C\ROOT_ID && in_array($_REQUEST['arg'],
                ['statistics', 'graphstats', 'editgroup'])))) ||
                (isset($_REQUEST['arg']) && $_REQUEST['arg'] == 'infogroup')) {
                $name = $group['GROUP_NAME'];
                $data['CURRENT_GROUP']['name'] = $name;
                $data['CURRENT_GROUP']['id'] = $group['GROUP_ID'];
                $data['CURRENT_GROUP']['owner'] = $group['OWNER'];
                $data['CURRENT_GROUP']['register'] =
                    $group['REGISTER_TYPE'];
                if ($info_no_access && $group['REGISTER_TYPE'] ==
                    C\INVITE_ONLY_JOIN) {
                    $group_id = false;
                }
                $data['CURRENT_GROUP']['member_access'] =
                    $group['MEMBER_ACCESS'];
                $data['CURRENT_GROUP']['vote_access'] =
                    $group['VOTE_ACCESS'];
                $data['CURRENT_GROUP']['post_lifetime'] =
                    $group['POST_LIFETIME'];
                $data['CURRENT_GROUP']['encryption'] =
                    $group['ENCRYPTION'];
                $is_owner = true;
            } else if (!in_array($_REQUEST['arg'],
                ["deletegroup", "joingroup", "unsubscribe"]) &&
                $_SESSION['USER_ID'] != C\ROOT_ID) {
                $group_id = null;
                $group = null;
            }
        } else if (isset($_REQUEST['name'])) {
            $name = substr(trim($parent->clean($_REQUEST['name'], "string")), 0,
                C\SHORT_TITLE_LEN);
            $data['CURRENT_GROUP']['name'] = $name;
            $group_id = null;
            $group = null;
        } else {
            $group_id = null;
            $group = null;
        }
        /* end ownership verify */
        $browse = false;
        $search_name = "manageGroups";
        if (isset($_REQUEST['browse']) && $_REQUEST['browse'] == 'true') {
            $browse = true;
            $data['browse'] = 'true';
            $search_name = "browseGroups";
            $this->initSocialBadges($_SESSION['USER_ID'], $data);
        }
        $data['FORM_TYPE'] = ($browse) ? "" : "addgroup";
        $data['CONTEXT'] = 'groups';
        if (!empty($_REQUEST['context']) && in_array($_REQUEST['context'],
            ['account', 'groups', "join_groups"])) {
            $data['CONTEXT'] = $_REQUEST['context'];
        }
        $data['USER_FILTER'] = "";
        if (isset($_REQUEST['arg']) &&
            in_array($_REQUEST['arg'], $possible_arguments)) {
            switch ($_REQUEST['arg']) {
                case "activateuser":
                    $_REQUEST['arg'] = "editgroup";
                    $num_activated = 0;
                    if ($is_owner && !empty($_REQUEST['user_ids'])) {
                        $user_ids = $_REQUEST['user_ids'];
                        $ids = explode("*", $user_ids);
                        foreach ($ids as $user_id) {
                            $user_id = (!empty($user_id)) ?
                                $parent->clean($user_id, 'int'): 0;
                            if ($group_model->checkUserGroup($user_id,
                                $group_id)) {
                                $group_model->updateStatusUserGroup($user_id,
                                    $group_id, C\ACTIVE_STATUS);
                                $num_activated++;
                            }
                        }
                    }
                    $this->getGroupUsersData($data, $group_id);
                    if ($num_activated == 1) {
                        return $parent->redirectWithMessage(
                            tl('social_component_user_activated'),
                            ["arg", 'context', 'end_row', 'group_limit',
                            'num_show', 'start_row', 'user_filter',
                            'user_sorts', "visible_users"]);
                    } else if ($num_activated > 1) {
                        return $parent->redirectWithMessage(
                            tl('social_component_users_activated'),
                            ["arg", 'context', 'end_row', 'group_limit',
                            'num_show', 'start_row', 'user_filter',
                            'user_sorts', "visible_users"]);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_no_user_activated'),
                        ["arg", 'context', 'end_row', 'group_limit',
                        'num_show', 'start_row', 'user_filter',
                        'user_sorts', "visible_users"]);
                case "addgroup":
                    if (($add_id = $group_model->getGroupId($name)) <= 0) {
                        return $parent->redirectWithMessage(
                            tl('social_component_group_doesnt_exist'),
                            ['arg', 'context', 'end_row',
                            'group_limit', 'name', 'num_show', 'start_row',
                            'user_filter', 'user_sorts',"visible_users"]);
                    }
                    $register =
                        $group_model->getRegisterType($add_id);
                    if ($register >= C\LOW_JOIN_FEE &&
                        !in_array(C\MONETIZATION_TYPE,
                        ['group_fees','fees_and_keywords'])) {
                        $register = C\INVITE_ONLY_JOIN;
                    }
                    if ((!empty($register) && $register !=C\INVITE_ONLY_JOIN) ||
                        $_SESSION['USER_ID'] == C\ROOT_ID) {
                        return $this->addGroup($data, $add_id, $register);
                    } else {
                        return $parent->redirectWithMessage(
                            tl('social_component_groupname_cant_add'));
                    }
                    break;
                case "banuser":
                    $_REQUEST['arg'] = "editgroup";
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context']=='search') {
                        $data['CONTEXT'] = 'search';
                    };
                    $banned = 0;
                    if ($is_owner && !empty($_REQUEST['user_ids'])) {
                        $user_ids = $_REQUEST['user_ids'];
                        $ids = explode("*", $user_ids);
                        foreach ($ids as $user_id) {
                            $user_id = (!empty($user_id)) ?
                                $parent->clean($user_id, 'int'): 0;
                            if ($group_model->checkUserGroup($user_id,
                                $group_id)) {
                                $group_model->updateStatusUserGroup($user_id,
                                    $group_id, C\SUSPENDED_STATUS);
                                $banned++;
                            }
                        }
                    }
                    $this->getGroupUsersData($data, $group_id);
                    if ($banned == 1) {
                        return $parent->redirectWithMessage(
                            tl('social_component_user_banned'),
                            ["arg", 'context', 'end_row', 'group_limit',
                            'num_show', 'start_row','user_filter',
                            'user_sorts',"visible_users"]);
                    } else  if ($banned > 1) {
                        return $parent->redirectWithMessage(
                            tl('social_component_users_banned'),
                            ["arg", 'context', 'end_row', 'group_limit',
                            'num_show', 'start_row', 'user_filter',
                            'user_sorts',"visible_users"]);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_no_user_banned'),
                        ["arg", 'context', 'end_row', 'group_limit',
                        'num_show', 'start_row', 'user_filter',
                        'user_sorts',"visible_users"]);
                case "creategroup":
                    if ($_SESSION['USER_ID'] == C\PUBLIC_USER_ID) {
                        return $parent->redirectWithMessage(
                            tl('social_component_public_cant_create'));
                    } else if ($group_model->getGroupId($name) > 0) {
                        return $parent->redirectWithMessage(
                            tl('social_component_groupname_exists'));
                    } else if (!empty($name)) {
                        $group_fields = [
                            "member_access" => ["ACCESS_CODES", C\GROUP_READ],
                            "register" => ["REGISTER_CODES", C\REQUEST_JOIN],
                            "vote_access" => ["VOTING_CODES",
                                C\NON_VOTING_GROUP],
                            "post_lifetime" => ["POST_LIFETIMES", C\FOREVER],
                            "encryption" => ["ENCRYPTION_CODES", 0]
                        ];
                        foreach ($group_fields as $field => $info) {
                            if (!isset($_REQUEST[$field]) ||
                                !in_array($_REQUEST[$field],
                                array_keys($data[$info[0]]))) {
                                $_REQUEST[$field] = $info[1];
                            }
                        }
                        $group_model->addGroup($name,
                            $_SESSION['USER_ID'], $_REQUEST['register'],
                            $_REQUEST['member_access'],
                            $_REQUEST['vote_access'],
                            $_REQUEST['post_lifetime'],
                            $_REQUEST['encryption']);
                        //one exception to setting $group_id
                        $group_id = $group_model->getGroupId($name);
                        return $parent->redirectWithMessage(
                            tl('social_component_groupname_created'),
                            ["arg", 'start_row', 'end_row', 'num_show']);
                    }
                    break;
                case "deletegroup":
                    $_REQUEST['arg'] = empty($_REQUEST['context']) ?
                        'none': 'search';
                    $data['CURRENT_GROUP'] = $default_group;
                    if ( $group_id <= 0) {
                        return $parent->redirectWithMessage(
                          tl('social_component_groupname_doesnt_exists'),
                          ["arg"]);
                    } else if (($group &&
                        $group['OWNER_ID'] == $_SESSION['USER_ID']) ||
                        $_SESSION['USER_ID'] == C\ROOT_ID) {
                        $group_model->deleteGroup($group_id);
                        return $parent->redirectWithMessage(
                            tl('social_component_group_deleted'), ["arg"]);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_no_delete_group'),
                        ["arg", 'start_row', 'end_row', 'num_show']);
                case "deleteuser":
                    $_REQUEST['arg'] = "editgroup";
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context']=='search') {
                        $data['CONTEXT'] = 'search';
                    }
                    $deleted = 0;
                    if ($is_owner && !empty($_REQUEST['user_ids'])) {
                        $user_ids = $_REQUEST['user_ids'];
                        $ids = explode("*", $user_ids);
                        foreach ($ids as $user_id) {
                            $user_id = (!empty($user_id)) ?
                                $parent->clean($user_id, 'int'): 0;
                            if ($group_model->deletableUser($user_id,
                                $group_id)) {
                                $group_model->deleteUserGroup(
                                    $user_id, $group_id);
                                $deleted++;
                            }
                        }
                    }
                    if ($deleted == 1) {
                        return $parent->redirectWithMessage(
                            tl('social_component_user_deleted'),
                            ["arg", 'context', 'end_row', 'group_limit',
                            'num_show', 'start_row', 'user_filter',
                            'user_sorts',"visible_users"]);
                    } else if ($deleted > 1) {
                        return $parent->redirectWithMessage(
                            tl('social_component_users_deleted'),
                            ["arg", 'context', 'end_row', 'group_limit',
                            'num_show', 'start_row', 'user_filter',
                            'user_sorts',"visible_users"]);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_no_delete_user_group'),
                        ["arg", 'context', 'end_row', 'group_limit',
                        'num_show', 'start_row', 'user_filter',
                        'user_sorts',"visible_users"]);
                case "editgroup":
                    if (!$group_id ||
                        (!$is_owner && $_SESSION['USER_ID'] != C\ROOT_ID)) {
                        break;
                    }
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context']=='search') {
                        $data['CONTEXT'] = 'search';
                    }
                    $data['FORM_TYPE'] = "editgroup";
                    $update_fields = [
                        ['owner', "OWNER", "valid_user"],
                        ['member_access', 'MEMBER_ACCESS', 'ACCESS_CODES'],
                        ['register', 'REGISTER_TYPE','REGISTER_CODES'],
                        ['vote_access', 'VOTE_ACCESS', 'VOTING_CODES'],
                        ['post_lifetime', 'POST_LIFETIME', 'POST_LIFETIMES'],
                        ['encryption', 'ENCRYPTION', 'ENCRYPTION_CODES']
                        ];
                    $message = $this->updateGroup($data, $group,
                        $update_fields);
                    if (!empty($message)) {
                        $preserve_fields = ['arg', 'browse', 'context',
                            'start_row','end_row', 'group_limit', 'num_show',
                            'user_filter', 'user_sorts', 'visible_users'];
                        if ($message == tl('social_component_owner_updated')) {
                            $preserve_fields = ['arg', 'browse', 'start_row',
                                'end_row', 'group_limit', 'num_show',
                                'user_filter', 'user_sorts', 'visible_users'];
                        }
                        return $parent->redirectWithMessage($message,
                            $preserve_fields);
                    }
                    $data['CURRENT_GROUP']['register'] =
                        $group['REGISTER_TYPE'];
                    $data['CURRENT_GROUP']['member_access'] =
                        $group['MEMBER_ACCESS'];
                    $data['CURRENT_GROUP']['vote_access'] =
                        $group['VOTE_ACCESS'];
                    $data['CURRENT_GROUP']['post_lifetime'] =
                        $group['POST_LIFETIME'];
                    $data['CURRENT_GROUP']['encryption'] =
                        $group['ENCRYPTION'];
                    $data['SCRIPT'] .= "listenAll('input.user-id', 'click',".
                        " updateCheckedUserIds);";
                    $this->getGroupUsersData($data, $group_id);
                    if (!empty($_FILES['DISCUSSION_DATA']['tmp_name'])) {
                        if (empty($_FILES['DISCUSSION_DATA']['data'])) {
                            $feed_data = $parent->web_site->fileGetContents(
                                $_FILES['DISCUSSION_DATA']['tmp_name']);
                        } else {
                            $feed_data = $_FILES['DISCUSSION_DATA']['data'];
                        }
                        $this->importDiscussions($group_id,
                            $group['OWNER_ID'], $feed_data);
                    }
                    $data['SCRIPT'] .= "elt('focus-button').focus();";
                    break;
                case "infogroup":
                    if (!$group_id) {
                        return $parent->redirectWithMessage(
                          tl('social_component_groupname_lookup_error'));
                    }
                    $user_model = $parent->model("user");
                    $data['FORM_TYPE'] = "infogroup";
                    $this->getGroupUsersData($data, $group_id);
                    $owner_id = $user_model->getUserId(
                        $data['CURRENT_GROUP']['owner']);
                    $data['CURRENT_GROUP']['owner_id'] = $owner_id;
                    $data['CURRENT_GROUP']['user_icon'] =
                        $user_model->getUserIconUrl($owner_id);
                    break;
                case "inviteusers":
                    $data['FORM_TYPE'] = "inviteusers";
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context']=='search') {
                        $data['CONTEXT'] = 'search';
                    }
                    if (isset($_REQUEST['users_names']) && $is_owner) {
                        $users_string = $parent->clean($_REQUEST['users_names'],
                            "string");
                        $pre_user_names = preg_split("/\s+|\,/", $users_string);
                        $users_invited = false;
                        foreach ($pre_user_names as $user_name) {
                            $user_name = trim($user_name);
                            $user = $parent->model("user")->getUser($user_name);
                            if ($user) {
                                if (!$group_model->checkUserGroup(
                                    $user['USER_ID'], $group_id)) {
                                    $group_model->addUserGroup(
                                        $user['USER_ID'], $group_id,
                                        C\INVITED_STATUS);
                                    $users_invited = true;
                                }
                            }
                        }
                        $_REQUEST['arg'] = "editgroup";
                        if ($users_invited) {
                            return $parent->redirectWithMessage(
                                tl('social_component_users_invited'),
                                ["arg", 'context', 'end_row', 'group_limit',
                                'num_show', 'start_row', 'user_filter',
                                'user_sorts',"visible_users"]);
                        } else {
                            return $parent->redirectWithMessage(
                                tl('social_component_no_users_invited'),
                                ["arg", 'context', 'end_row', 'group_limit',
                                'num_show', 'start_row', 'user_filter',
                                'user_sorts',"visible_users"]);
                        }
                    }
                    break;
                case "joingroup":
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context'] == 'search') {
                        $_REQUEST['arg'] = 'search';
                    } else {
                        $_REQUEST['arg'] = 'none';
                    }
                    $user_id = (isset($_REQUEST['user_id'])) ?
                        $parent->clean($_REQUEST['user_id'], 'int'): 0;
                    if (empty($group_id) && !empty($_REQUEST['name'])) {
                        $group_id = $group_model->getGroupId(
                            $parent->clean($_REQUEST['name'], 'string'));
                    }
                    if ($user_id && $group_id &&
                        $group_model->checkUserGroup($user_id,
                            $group_id, C\INVITED_STATUS)) {
                        $group_model->updateStatusUserGroup($user_id,
                            $group_id, C\ACTIVE_STATUS);
                        return $parent->redirectWithMessage(
                            tl('social_component_joined'), ['arg']);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_no_join'),
                        ['arg', 'browse', 'start_row', 'end_row',
                        'num_show']);
                case "graphstats":
                    if (!$group_id || (!$is_owner &&
                        $_SESSION['USER_ID'] != C\ROOT_ID)) {
                        break;
                    }
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context']=='search') {
                        $data['CONTEXT'] = 'search';
                    };
                    $period = C\ONE_DAY;
                    if (in_array($_REQUEST['time'], [C\ONE_DAY, C\ONE_MONTH,
                        C\ONE_YEAR])) {
                        $period = $_REQUEST['time'];
                    }
                    $impression_type = C\THREAD_IMPRESSION;
                    if (in_array($_REQUEST['impression'], [C\THREAD_IMPRESSION,
                        C\WIKI_IMPRESSION, C\GROUP_IMPRESSION,
                        C\QUERY_IMPRESSION])) {
                        $impression_type = $_REQUEST['impression'];
                    }
                    $item_id = $parent->clean($_REQUEST['item'], "int");
                    $this->makeImpressionChart($data, $impression_type,
                        $period, $item_id);
                    $this->getGroupUsersData($data, $group_id);
                    $data['SCRIPT'] .= "elt('focus-button').focus();";
                    break;
                case "memberaccess":
                    $update_fields = [
                        ['memberaccess', 'MEMBER_ACCESS', 'ACCESS_CODES']];
                    $message =
                        $this->updateGroup($data, $group, $update_fields);
                    $_REQUEST['arg'] = empty($_REQUEST['context']) ?
                        'none': 'search';
                    unset($_REQUEST['group_id']);
                    return $parent->redirectWithMessage($message,
                        ['arg', 'browse', 'start_row', 'end_row',
                        'num_show', 'visible_users', 'user_filter']);
                case "postlifetime":
                    $update_fields = [
                        ['postlifetime', 'POST_LIFETIME', 'POST_LIFETIMES']];
                    $message =
                        $this->updateGroup($data, $group, $update_fields);
                    $_REQUEST['arg'] = empty($_REQUEST['context']) ?
                        'none': 'search';
                    unset($_REQUEST['group_id']);
                    return $parent->redirectWithMessage($message,
                        ['arg', 'browse', 'start_row', 'end_row',
                        'num_show', 'visible_users', 'user_filter']);
                case "voteaccess":
                    $update_fields = [
                        ['voteaccess', 'VOTE_ACCESS', 'VOTING_CODES']];
                    $message =
                        $this->updateGroup($data, $group, $update_fields);
                    $_REQUEST['arg'] = empty($_REQUEST['context']) ?
                        'none': 'search';
                    unset($_REQUEST['group_id']);
                    return $parent->redirectWithMessage($message,
                        ['arg', 'browse', 'start_row', 'end_row',
                        'num_show', 'visible_users', 'user_filter']);
                case "registertype":
                    $update_fields = [
                        ['registertype', 'REGISTER_TYPE',
                            'REGISTER_CODES']];
                    $message =
                        $this->updateGroup($data, $group, $update_fields);
                    $_REQUEST['arg'] = empty($_REQUEST['context']) ?
                        'none': 'search';
                    unset($_REQUEST['group_id']);
                    return $parent->redirectWithMessage($message,
                        ['arg', 'browse', 'start_row', 'end_row',
                        'num_show', 'visible_users', 'user_filter']);
                case "statistics":
                    if (!$group_id || (!$is_owner &&
                        $_SESSION['USER_ID'] != C\ROOT_ID)) {
                            break;
                    }
                    if (!empty($_REQUEST['context']) &&
                        $_REQUEST['context']=='search') {
                        $data['CONTEXT'] = 'search';
                    };
                    $data['FORM_TYPE'] = "statistics";
                    $impression_model = $parent->model("impression");
                    $periods = [C\ONE_HOUR, C\ONE_DAY, C\ONE_MONTH, C\ONE_YEAR,
                        C\FOREVER];
                    $stat_types = [C\GROUP_IMPRESSION, C\THREAD_IMPRESSION,
                        C\WIKI_IMPRESSION];
                    $filter = (empty($_REQUEST['filter'])) ? "" :
                        $parent->clean($_REQUEST['filter'], 'string');
                    $data['FILTER'] = $filter;
                    foreach ($periods as $period) {
                        $data["STATISTICS"][C\GROUP_IMPRESSION][$period] =
                            $impression_model->getStatistics(C\GROUP_IMPRESSION,
                            $period, $filter, $group_id);
                        $data["STATISTICS"][C\THREAD_IMPRESSION][$period] =
                            $impression_model->getStatistics(
                            C\THREAD_IMPRESSION, $period,  $filter, $group_id);
                        $data["STATISTICS"][C\WIKI_IMPRESSION][$period] =
                            $impression_model->getStatistics(C\WIKI_IMPRESSION,
                            $period, $filter, $group_id);
                    }
                    if (C\DIFFERENTIAL_PRIVACY) {
                        $this->socialPrivacy($data);
                    }
                    $this->getGroupUsersData($data, $group_id);
                    $data['SCRIPT'] .= "elt('focus-button').focus();";
                    break;
                case "unsubscribe":
                    if (!empty($_REQUEST['context'])) {
                        $_REQUEST['arg'] = 'search';
                    } else {
                        $_REQUEST['arg'] = 'none';
                    }
                    $user_id = (isset($_REQUEST['user_id'])) ?
                        $parent->clean($_REQUEST['user_id'], 'int'): 0;
                    echo $_REQUEST['user_id'] . " " . $group_id;
                    if ($user_id && $group_id &&
                        $group_model->checkUserGroup($user_id,
                        $group_id)) {
                        $group_model->deleteUserGroup($user_id,
                            $group_id);
                        return $parent->redirectWithMessage(
                            tl('social_component_unsubscribe'),
                            ['arg','start_row', 'end_row', 'num_show']);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_no_unsubscribe'),
                        ['arg', 'start_row', 'end_row', 'num_show']);
            }
        }
        $current_id = $_SESSION["USER_ID"];
        $this->initSocialBadges($current_id, $data);
        $data['group_sorts'] = [ "name_asc" =>
            html_entity_decode(tl('social_component_name_asc')),
            "name_desc" => html_entity_decode(tl('social_component_name_desc')),
            "newest_asc" =>
                html_entity_decode(tl('social_component_newest_asc')),
            "newest_desc" =>
                html_entity_decode(tl('social_component_newest_desc')),
        ];
        $data['GROUP_SORT'] =  "name_asc";
        if (!empty($_REQUEST['group_sort']) && in_array($_REQUEST['group_sort'],
            array_keys($data['group_sorts']))) {
            $data['GROUP_SORT'] = $_REQUEST['group_sort'];
        }
        $data["GROUP_FILTER"] = (empty($_REQUEST['group_filter'])) ?
            "" : $parent->clean($_REQUEST['group_filter'], "string");
        $search_array = [];
        if ($data["GROUP_FILTER"]) {
            if ($data["GROUP_FILTER"][0] == '=') {
                $name_clause = ["name", "=", substr($data["GROUP_FILTER"],1)];
            } else {
                $name_clause = ["name", "CONTAINS", $data["GROUP_FILTER"]];
            }
        } else {
            $name_clause = ["name", "", ""];
        }
        $name_clause[3] = ($data['GROUP_SORT'] == "name_asc") ?
            "ASC" : ($data['GROUP_SORT'] == "name_desc" ? "DESC" : "");
        if ($name_clause != ["name", "", "", ""]) {
            $search_array[] = $name_clause;
        }
        $newest_clause = ["created_time", "", ""];
        $newest_clause[3] = ($data['GROUP_SORT'] == "newest_asc") ?
            "ASC" : ($data['GROUP_SORT'] == "newest_desc" ? "DESC" : "");
        if ($newest_clause != ["created_time", "", "", ""]) {
            $search_array[] = $newest_clause;
        }
        if (isset($_SESSION['MAX_PAGES_TO_SHOW']) &&
            $_SESSION['MAX_PAGES_TO_SHOW'] > 0) {
            $results_per_page = $_SESSION['MAX_PAGES_TO_SHOW'];
        } else {
            $results_per_page = C\NUM_RESULTS_PER_PAGE;
        }
        $data['RESULTS_PER_PAGE'] = $results_per_page;
        $_REQUEST['start_row'] = $_REQUEST['limit'] ?? 0;
        $parent->pagingLogic($data, $group_model,
            "GROUPS", $results_per_page, $search_array, "",
            [$current_id, $browse]);
        $data['LIMIT'] = $data['START_ROW'];
        $this->addActivityInfoToGroups($data);
        return $data;
    }
    /**
     * Used to handle request related to usage statistics for groups
     *
     * @param array &$data fields to be sent to the view with chart data
     * @param int $impression_type what type $item_id is. Could have values:
     *  C\WIKI_IMPRESSION, C\THREAD_IMPRESSION, C\GROUP_IMPRESSION,
     *  C\QUERY_IMPRESSION
     * @param int $period C\ONE_HOUR, C\ONE_DAY, etc that the chart is drawn
     *  should be for
     * @param int $item_id id of group, wiki page, thread, chart should be for
     */
    public function makeImpressionChart(&$data, $impression_type,
        $period, $item_id, $chart_name = "chart", $chart_id = "chart")
    {
        $parent = $this->parent;
        $data['FORM_TYPE'] = "graphstats";
        $data['INCLUDE_SCRIPTS'][] = "chart";
        $impression_model = $parent->model("impression");
        $data["STATISTICS"][$period] =
            $impression_model->getPeriodHistogramData(
            $impression_type, $period, $item_id);
        $graph_data = [];
        if ($period == C\ONE_DAY) {
            $column_name = tl('social_component_hour');
            $dt_format = "H";
            $now = date("H");
            for ($i = 0 ; $i < 24; $i++) {
                $graph_data[" ".(($now + $i) % 24 + 1)] = 0;
            }
        } else if ($period == C\ONE_MONTH) {
            $column_name = tl('social_component_day');
            $dt_format = "d";
            $now = date("d");
            for ($i = 0 ; $i < 31; $i++) {
                $graph_data[" ".(($now + $i) % 31 +1)] = 0;
            }
        } else if ($period == C\ONE_YEAR) {
            $column_name = tl('social_component_month');
            $dt_format = "M";
            $now = date("n");
            $months = [tl('social_component_jan'),
                tl('social_component_feb'),
                tl('social_component_mar'),
                tl('social_component_apr'),
                tl('social_component_may'),
                tl('social_component_jun'),
                tl('social_component_jul'),
                tl('social_component_aug'),
                tl('social_component_sep'),
                tl('social_component_oct'),
                tl('social_component_nov'),
                tl('social_component_dec')];
            for ($i = 0 ; $i < 12; $i++) {
                $graph_data[$months[(($now + $i) % 12 )]] = 0;
            }
        }
        $graph_title = tl('social_component_visits', $column_name);
        foreach ($data['STATISTICS'][$period] as $key =>
            $statistics_value) {
            $timestamp = $statistics_value['UPDATE_TIMESTAMP'];
            $dt = date($dt_format, $timestamp);
            if ($period != C\ONE_YEAR) {
                $graph_data[" ".intval($dt)] =
                    $statistics_value['VIEWS'];
            } else {
                $graph_data[$dt] =
                    $statistics_value['VIEWS'];
            }
        }
        $graph_data = json_encode($graph_data);
        if ($_SERVER["MOBILE"]) {
            $properties = ["title" => $graph_title,
                "width" => 340, "height" => 300,
                "tick_font_size" => 8];
        } else {
            $properties = ["title" => $graph_title,
                "width" => 700, "height" => 500];
        }
        $properties = json_encode($properties);
        $data['SCRIPT'] .= "$chart_name = new Chart(" .
            '"'. $chart_id . '", '. $graph_data .
            ', '. $properties . "); $chart_name.draw();";
    }
    /**
     * Used to add Differential Privacy for each group
     *
     * @param object &$data contains fields which will be sent to the view
     */
    public function socialPrivacy(&$data)
    {
        $parent = $this->parent;
        $impression_model = $parent->model("impression");
        foreach ($stat_types as $field) {
            $i = 0;
            foreach ($periods as  $period) {
                if ($field == C\GROUP_IMPRESSION) {
                    if (!empty($data["STATISTICS"][$field][$period])) {
                        $view_stat = $impression_model->getImpressionStat(
                            $data["STATISTICS"][$field][$period][0]['ID'],
                            $field, $period);
                        $fuzzy_views = $view_stat[1];
                        if (empty($view_stat[0]) ||
                            $view_stat[0] != $data["STATISTICS"][$field][
                            $period][0]['NUM_VIEWS'] ||
                            $tmp_data[$i - 1] > $fuzzy_views) {
                            $fuzzy_views = $parent->addDifferentialPrivacy(
                                $data["STATISTICS"][$field][
                                $period][0]['NUM_VIEWS']);
                            /* Make sure each time period's
                            fuzzified view is at least as large as
                            previous time period's value */
                            if ($i > 0) {
                                if ($tmp_data[$i - 1] > $fuzzy_views) {
                                    $fuzzy_views = $tmp_data[$i - 1];
                                }
                            }
                            $impression_model->updateImpressionStat(
                                $data["STATISTICS"][$field][$period][0]['ID'],
                                $field, $period, $data["STATISTICS"][$field][
                                    $period][0]['NUM_VIEWS'],
                                $fuzzy_views);
                        }
                        $data["STATISTICS"][$field][$period][0]
                            ['NUM_VIEWS'] = ($fuzzy_views == 0)?
                            tl('managegroups_element_no_activity'):
                            $fuzzy_views;
                        $tmp_data[$i] = $fuzzy_views;
                        $i++;
                    }
                } else {
                    if (!empty($data['STATISTICS'][$field][$period])) {
                        foreach ($data['STATISTICS'][$field]
                            [$period] as $item_name =>
                            $item_data) {
                            $view_stat = $impression_model->getImpressionStat(
                                $item_data[0]['ID'], $field, $period);
                            $fuzzy_views = $view_stat[1];
                            if ($view_stat[0] != $item_data[0]['NUM_VIEWS'] ||
                                $tmp_data[$item_name][$i - 1] > $fuzzy_views) {
                                $fuzzy_views = $parent->addDifferentialPrivacy(
                                    $item_data[0]['NUM_VIEWS']);
                                if ($i > 0) {
                                    if ($tmp_data[$item_name][$i-1] >
                                        $fuzzy_views) {
                                        $fuzzy_views =
                                            $tmp_data[$item_name][$i - 1];
                                    }
                                }
                                $impression_model->updateImpressionStat(
                                    $item_data[0]['ID'],
                                    $field, $period, $item_data[0]['NUM_VIEWS'],
                                    $fuzzy_views);
                            }
                            $data["STATISTICS"][$field][$period][
                                $item_name][0]['NUM_VIEWS']=
                                ($fuzzy_views == 0) ?
                                tl('managegroups_element_no_activity'):
                                $fuzzy_views;
                            $tmp_data[$item_name][$i] =
                                $fuzzy_views;
                            /* Decrypt group items if encrypted before
                                displaying */
                            if ($field == C\THREAD_IMPRESSION &&
                                $group_model->isGroupEncrypted($group_id)) {
                                // Decrypt thread's title
                                $key = $group_model->getGroupKey($group_id);
                                $decrypted_item_name = $group_model->decrypt(
                                    $item_name, $key);
                                $data['STATISTICS'][$field][$period][
                                    $decrypted_item_name] = $item_data;
                                unset($data['STATISTICS'][$field][$period][
                                    $item_name]);
                            }
                        }
                        $i++;
                    }
                }
            }
        }
    }
    /**
     * Used to import group discussion thread from another grouping or bulletin
     * site that has the ability to show the group as rss or atom. Examples
     * of such site are: phpBB, google groups, phorum
     *
     * @param int $group_id id of group that thread post data will be imported
     *  into
     * @param int $user_id id of person doing the importing (should be
     *  owner of group)
     * @param string $feed_data an rss or atom feed containing forum/group posts
     */
    public function importDiscussions($group_id, $user_id, $feed_data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $page = preg_replace('@<(/?)(\w+\s*)\:@u', '<$1', $feed_data);
        $page = preg_replace("@<link@", "<slink", $page);
        $page = preg_replace("@</link@", "</slink", $page);
        $page = preg_replace("@pubDate@i", "pubdate", $page);
        $page = preg_replace("@&lt;@", "<", $page);
        $page = preg_replace("@&gt;@", ">", $page);
        $page = preg_replace("@<br(\s[^>]*)*\/?>@i", "[br]", $page);
        $page = preg_replace("@<hr(\s[^>]*)*\/?>@i", "[hr]", $page);
        $page = preg_replace("@<\/?i(\s[^>]*)*>@", "''", $page);
        $page = preg_replace("@<\/?b(\s[^>]*)*>@",  "'''", $page);
        $page = preg_replace("@<\/?u(\s[^>]*)*>@",  "'''", $page);
        $page = preg_replace("@<\/?tt(\s[^>]*)*>@",  "'''", $page);
        $page = preg_replace("@<p(\s[^>]*)*>@", "\n\n", $page);
        $page = preg_replace("@<\/p(\s[^>]*)*>@", "\n", $page);
        $page = preg_replace('@<\/?(o|u)l(\s[^>]*)*>@', "\n\n", $page);
        $page = preg_replace("@<li(\s[^>]*)*>@", "*", $page);
        $page = preg_replace("@<\/li(\s[^>]*)*>@", "\n", $page);
        $page = preg_replace("@<!\[CDATA\[(.+?)\]\]>@s", '$1', $page);
        $dom = L\getDomFromString($page);
        $rss_elements = ["title" => "title",
            "description" => "description", "link" =>"slink",
            "author" => ["author", "creator"],
            "guid" => "guid", "pubdate" => "pubdate"];
        $nodes = $dom->getElementsByTagName('item');
        if ($nodes->length == 0) {
            // maybe we're dealing with atom rather than rss
            $nodes = $dom->getElementsByTagName('entry');
            $rss_elements = [
                "title" => "title", "description" => ["summary", "content"],
                "link" => "slink", "guid" => "id",
                "author" => ["author", "creator"],
                "pubdate" => "updated"];
        }
        $num_added = 0;
        $num_seen = 0;
        $items = [];
        $feed_types = ['phpbb', 'googlegroup', 'phorum'];
        $feed_type = 'unknown';
        $i = 0;
        foreach ($nodes as $node) {
            $item = [];
            foreach ($rss_elements as $db_element => $feed_element) {
                if (!is_array($feed_element)) {
                    $feed_element = [$feed_element];
                }
                foreach ($feed_element as $tag_name) {
                    $tag_node = $node->getElementsByTagName(
                            $tag_name)->item(0);
                    $element_text = (is_object($tag_node)) ?
                        $tag_node->nodeValue: "";
                    if ($element_text) {
                        break;
                    }
                }
                if ($db_element == "link" && $tag_node && $element_text == "") {
                    $element_text = $tag_node->getAttribute("href");
                }
                $element_text = htmlentities(strip_tags($element_text));
                $element_text = preg_replace('/\[br\]/', "<br>", $element_text);
                $element_text = preg_replace('/\[hr\]/', "<hr>", $element_text);
                $item[$db_element] = $element_text;
            }
            if ($feed_type == 'unknown') {
                if (stripos($item['link'], 'viewtopic.php') !== false) {
                    $feed_type = 'phpbb';
                } else if (stripos($item['link'],'groups.google.com')
                    !==false) {
                    $feed_type = 'googlegroup';
                } else if (stripos($item['link'], 'read.php') !== false) {
                    $feed_type = 'phorum';
                }
            }
            switch ($feed_type) {
                case 'phpbb':
                    if (@preg_match('/t\=(\d+)/', $item['link'], $match)
                        !== false) {
                        $item['thread'] = $match[1];
                    }
                    if (($pos = strrpos($item['description'], 'Statistics:'))
                        !== false) {
                        $item['description'] = substr($item['description'], 0,
                            $pos);
                    }
                    if (($pos = strrpos($item['title'], '&bull;'))
                        !== false) {
                        $item['title'] = trim(substr($item['title'], $pos + 6));
                    }
                    break;
                case 'googlegroup':
                    if (@preg_match('@/d/msg/.*/(.*)/@', $item['link'], $match)
                        !== false) {
                        $item['thread'] = $match[1];
                    }
                    break;
                case 'phorum':
                    if (@preg_match('@read\.php\?.*\,(.*)\,@', $item['link'],
                        $match) !== false) {
                        $item['thread'] = $match[1];
                    }
                    break;
                default:
                    $item['thread'] = $i;
            }
            $i++;
            $pos = (strtotime($item['pubdate'], 0)) ?
                strtotime($item['pubdate'], 0) : $i;
            $items[$pos] = $item;
        }
        set_error_handler(C\NS_CONFIGS . "yioop_error_handler");
        ksort($items);
        $threads = [];
        foreach ($items as $item) {
            $parent_id = 0;
            if (!empty($item['thread']) &&
                !empty($threads[$item['thread']])) {
                $parent_id = $threads[$item['thread']];
                $item['title'] = preg_replace("/^Re\:/", "--",
                    trim($item['title']), 1);
            }
            $post_prefix = "";
            $post_user_id = $group_model->getUserId($item['author']);
            if (!$post_user_id) {
                $post_user_id = $user_id;
                $post_prefix .= "'''" .
                    tl('social_component_originally_posted', $item['author']) .
                    "'''\n\n";
            }
            if (!$timestamp = strtotime($item['pubdate'], 0)) {
                $timestamp = time();
                $post_prefix .= "'''" .
                    tl('social_component_originally_dated', $item['pubdate']) .
                    "'''\n\n";
            }
            $thread_id = $group_model->addGroupItem(
                $parent_id, $group_id, $post_user_id, $item['title'],
                $parent->clean($post_prefix . $item['description'], "string"),
                C\STANDARD_GROUP_ITEM, $timestamp);
            if ($parent_id == 0) {
                $threads[$item['thread']] = $thread_id;
            }
        }
    }
    /**
     * Used to add a group to a user's list of group or to request
     * membership in a group if the group is By Request or Public
     * Request
     *
     * @param array &$data field variables to be drawn to view,
     *      we modify the SCRIPT component of this with a message
     *      regarding success of not of add attempt.
     * @param int $add_id group id to be added
     * @param int $register the registration type of the group
     */
    public function addGroup(&$data, $add_id, $register)
    {
        $parent = $this->parent;
        $group_model = $parent->model('group');
        $credit_model = $parent->model('credit');
        $user_id = $_SESSION['USER_ID'];
        $join_type = (($register == C\REQUEST_JOIN ||
            $register == C\PUBLIC_BROWSE_REQUEST_JOIN) &&
            $_SESSION['USER_ID'] != C\ROOT_ID) ?
            C\INACTIVE_STATUS : C\ACTIVE_STATUS;
            // if register fee or anyone can join will be active_status
        if ($register >= C\LOW_JOIN_FEE && in_array(C\MONETIZATION_TYPE,
            ['group_fees','fees_and_keywords'])) {
            $balance = $credit_model->getCreditBalance($user_id);
            if ($balance - $register < 0) {
                return $parent->redirectWithMessage(
                    tl('social_component_buy_more_credits_join'));
            }
            $group_name = $group_model->getGroupName($add_id);
            $strings_to_translate_for_model =
                [tl('social_component_join_group_fee')];
            $credit_model->updateCredits($user_id, -$register,
                'social_component_join_group_fee');
        }
        $group_model->addUserGroup($user_id, $add_id, $join_type);
        if ($join_type == C\ACTIVE_STATUS) {
            return $parent->redirectWithMessage(tl('social_component_joined'),
                ['browse', 'start_row', 'end_row', 'num_show']);
        }
        // if account needs to be activated email owner
        $group_info = $group_model->getGroupById($add_id,
            C\ROOT_ID);
        $user_model = $parent->model("user");
        $owner_info = $user_model->getUser(
            $group_info['OWNER']);
        $server = new MailServer(C\MAIL_SENDER, C\MAIL_SERVER,
            C\MAIL_SERVERPORT, C\MAIL_USERNAME, C\MAIL_PASSWORD,
            C\MAIL_SECURITY);
        $subject = tl('social_component_activate_group',
            $group_info['GROUP_NAME']);
        $current_username = $user_model->getUserName(
            $_SESSION['USER_ID']);
        $edit_user_url = C\NAME_SERVER . "?c=admin&a=manageGroups".
            "&arg=editgroup&group_id=$add_id&visible_users=true".
            "&user_filter=$current_username&preserve=true";
        $body = tl('social_component_activate_body',
            $current_username,
            $group_info['GROUP_NAME'])."\n".
            $edit_user_url . "\n\n".
            tl('social_component_notify_closing')."\n".
            tl('social_component_notify_signature');
        $message = tl(
            'social_component_notify_salutation',
            $owner_info['USER_NAME'])."\n\n";
        $message .= $body;
        $server->send($subject, C\MAIL_SENDER,
            $owner_info['EMAIL'], $message);
        return $parent->redirectWithMessage(
            tl('social_component_group_request_join'),
            ['browse', 'start_row', 'end_row', 'num_show']);
    }
    /**
     * Uses $_REQUEST and $user_id to look up all the users that a group
     * has to subject to $_REQUEST['user_limit'] and
     * $_REQUEST['user_filter']. Information about these roles is added as
     * fields to $data[NUM_USERS_GROUP'] and $data['GROUP_USERS']
     *
     * @param array &$data data for the manageGroups view.
     * @param int $group_id group to look up users for
     */
    public function getGroupUsersData(&$data, $group_id)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $data['visible_users'] = $_REQUEST['visible_users'] ?? 'false';
        $data['USER_SORTS'] = (empty($_REQUEST['user_sorts'])) ? [] :
            json_decode(urldecode($_REQUEST['user_sorts']), true);
        if ($data['USER_SORTS'] === null) {
            $data['USER_SORTS'] = json_decode(html_entity_decode(
                urldecode($_REQUEST['user_sorts'])), true);
            $data['USER_SORTS'] = ($data['USER_SORTS']) ? $data['USER_SORTS'] :
                [];
        }
        if ($data['visible_users'] == 'false') {
            unset($_REQUEST['user_filter']);
            unset($_REQUEST['user_limit']);
        }
        if (isset($_REQUEST['user_filter'])) {
            $user_filter = substr($parent->clean(
                $_REQUEST['user_filter'], 'string'), 0, C\NAME_LEN);
        } else {
            $user_filter = "";
        }
        $data['USER_FILTER'] = $user_filter;
        $num_users =  $group_model->countGroupUsers($group_id, $user_filter);
        if (C\DIFFERENTIAL_PRIVACY) {
            $data['NUM_USERS_GROUP'] = $parent->addDifferentialPrivacy(
                $num_users);
        } else {
            $data['NUM_USERS_GROUP'] = $num_users;
        }
        if (isset($_REQUEST['group_limit'])) {
            $group_limit = min($parent->clean(
                $_REQUEST['group_limit'], 'int'), $num_users);
            $group_limit = max($group_limit, 0);
        } else {
            $group_limit = 0;
        }
        $data['GROUP_LIMIT'] = $group_limit;
        $data['GROUP_USERS'] =
            $group_model->getGroupUsers($group_id, $user_filter,
            $data['USER_SORTS'], $group_limit);
    }
    /**
     * Used by $this->manageGroups to check and clean $_REQUEST variables
     * related to groups, to check that a user has the correct permissions
     * if the current group is to be modified, and if so, to call model to
     * handle the update
     *
     * @param array &$data used to add any information messages for the view
     *     about changes or non-changes to the model
     * @param array &$group current group which might be altered
     * @param array $update_fields which fields in the current group might be
     *     changed. Elements of this array are triples, the name of the
     *     group field, name of the request field to use for data, and an
     *     array of allowed values for the field
     */
    public function updateGroup(&$data, &$group, $update_fields)
    {
        $parent = $this->parent;
        $changed = false;
        if (!isset($group["OWNER_ID"]) ||
            ($group["OWNER_ID"] != $_SESSION['USER_ID'] &&
            $_SESSION['USER_ID'] != C\ROOT_ID)) {
            return tl('social_component_no_permission');
        }
        $group_id = $group["GROUP_ID"];
        $return_value = "";
        foreach ($update_fields as $row) {
            list($request_field, $group_field, $check_field) = $row;
            if (isset($_REQUEST[$request_field]) &&
                $check_field == "valid_user") {
                $new_owner_name = substr(
                    $parent->clean($_REQUEST['owner'],
                    'string'), 0, C\NAME_LEN);
                $new_owner = $parent->model("user")->getUser(
                    $new_owner_name);
                if (!isset($new_owner['USER_ID']) ) {
                    return tl('social_component_not_a_user');
                }
                if (!$parent->model("group")->checkUserGroup(
                    $new_owner['USER_ID'], $group_id)) {
                    return tl('social_component_not_in_group');
                }
                if ($group["OWNER_ID"] != $new_owner['USER_ID']) {
                    $group["OWNER_ID"] = $new_owner['USER_ID'];
                    $changed = true;
                    $return_value =
                        tl('social_component_owner_updated');
                }
            } else if (isset($_REQUEST[$request_field]) &&
                in_array($_REQUEST[$request_field],
                    array_keys($data[$check_field]))) {
                if ($group[$group_field] != $_REQUEST[$request_field]) {
                    $group[$group_field] =
                        $_REQUEST[$request_field];
                    $changed = true;
                    $return_value =
                        tl('social_component_group_updated');
                }
            } else if (!empty($_REQUEST[$request_field]) &&
                is_int($_REQUEST[$request_field])) {
                $return_value =
                    tl('social_component_unknown_access');
            }
        }
        if ($changed) {
            $parent->model("group")->updateGroup($group);
        }
        return $return_value;
    }
    /**
     * Used to support requests related to posting, editing, modifying,
     * and deleting group feed items.
     *
     * @return array $data fields to be used by GroupfeedElement
     */
    public function groupFeeds()
    {
        $parent = $this->parent;
        $controller_name = (get_class($parent) == C\NS_CONTROLLERS .
            "AdminController") ? "admin" : "group";
        $data["CONTROLLER"] = $controller_name;
        $group_model = $parent->model("group");
        $user_model = $parent->model("user");
        $cron_model = $parent->model("cron");
        $cron_time = $cron_model->getCronTime("cull_old_items");
        $delta = time() - $cron_time;
        if ($delta > C\ONE_HOUR) {
            $cron_model->updateCronTime("cull_old_items");
            $group_model->cullExpiredGroupItems();
        } else if ($delta == 0) {
            $cron_model->updateCronTime("cull_old_items");
        }
        $data["ELEMENT"] = "groupfeed";
        $data['SCRIPT'] = "";
        $data["INCLUDE_STYLES"] = ["editor"];
        if (isset($_SESSION['USER_ID'])) {
            $user_id = $_SESSION['USER_ID'];
        } else {
            $user_id = C\PUBLIC_USER_ID;
        }
        $username = $user_model->getUsername($user_id);
        if (isset($_REQUEST['num'])) {
            $results_per_page = $parent->clean($_REQUEST['num'], "int");
        } else if (isset($_SESSION['MAX_PAGES_TO_SHOW']) &&
            $_SESSION['MAX_PAGES_TO_SHOW'] > 0) {
            $results_per_page = $_SESSION['MAX_PAGES_TO_SHOW'];
        } else {
            $results_per_page = C\NUM_RESULTS_PER_PAGE;
        }
        if (isset($_REQUEST['limit'])) {
            $limit = $parent->clean($_REQUEST['limit'], "int");
        } else {
            $limit = 0;
        }
        if (isset($_SESSION['OPEN_IN_TABS'])) {
            $data['OPEN_IN_TABS'] = $_SESSION['OPEN_IN_TABS'];
        } else {
            $data['OPEN_IN_TABS'] = false;
        }
        $clean_array = [ "title" => "string", "description" => "string",
            "contact_id" => 'int', "just_group_id" => "int",
            "just_thread" => "int", "just_user_id" => "int"];
        $strings_array = [ "title" => C\TITLE_LEN, "description" =>
            C\MAX_GROUP_POST_LEN];
        if ($user_id == C\PUBLIC_USER_ID) {
            $_SESSION['LAST_ACTIVITY']['a'] = 'groupFeeds';
            $_SESSION['LAST_ACTIVITY']['c'] = $controller_name;
        } else {
            unset($_SESSION['LAST_ACTIVITY']);
        }
        foreach ($clean_array as $field => $type) {
            $$field = ($type == "string") ? "" : 0;
            if (isset($_REQUEST[$field])) {
                $tmp = $parent->clean($_REQUEST[$field], $type);
                if (isset($strings_array[$field])) {
                    $tmp = substr($tmp, 0, $strings_array[$field]);
                }
                if ($user_id == C\PUBLIC_USER_ID) {
                    $_SESSION['LAST_ACTIVITY'][$field] = $tmp;
                }
                $$field = $tmp;
            }
        }
        $possible_arguments = ["status"];
        if ($user_id !=  C\PUBLIC_USER_ID) {
            $user_messages_id = $group_model->getPersonalGroupId($user_id);
            $data['USER_MESSAGES_ID'] = $user_messages_id;
            $data['CONTACTS'] = array_diff($group_model->getGroupUserIds(
                $user_messages_id), [$user_id]);
            $possible_arguments = ["addcomment", "addcontact", "addgroup",
                "deletepost", "downvote", "newthread", "status", "updatepost",
                "upvote"];
        }
        if (isset($_REQUEST['arg']) &&
            in_array($_REQUEST['arg'], $possible_arguments)) {
            switch ($_REQUEST['arg']) {
                case "addcomment":
                    if (!empty($_REQUEST['page_type']) &&
                         $_REQUEST['page_type'] == "page_and_feedback") {
                         $_REQUEST['a'] = "wiki";
                         unset($_REQUEST['just_thread']);
                         unset($_REQUEST['limit']);
                         unset($_REQUEST['num']);
                    }
                    if (!isset($_REQUEST['parent_id'])
                        || !$_REQUEST['parent_id']
                        || !isset($_REQUEST['group_id'])
                        || !$_REQUEST['group_id']) {
                        return $parent->redirectWithMessage(
                            tl('social_component_comment_error'),
                            ['page_name']);
                    }
                    if (!$description) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_comment'),
                            ['page_name']);
                    }
                    $parent_id = $parent->clean($_REQUEST['parent_id'], "int");
                    $group_id = $parent->clean($_REQUEST['group_id'], "int");
                    $group = $group_model->getGroupById($group_id,
                        $user_id, true);
                    $read_comment = [C\GROUP_READ_COMMENT, C\GROUP_READ_WRITE,
                        C\GROUP_READ_WIKI];
                    if (!$group || $user_id == C\PUBLIC_USER_ID ||
                        ($group["OWNER_ID"] != $user_id &&
                        !in_array($group["MEMBER_ACCESS"], $read_comment) &&
                        $user_id != C\ROOT_ID)) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_post_access'),
                            ['page_name']);
                    }
                    if ($parent_id >= 0) {
                        $parent_item = $group_model->getGroupItem($parent_id);
                        if (!$parent_item) {
                            return $parent->redirectWithMessage(
                                tl('social_component_no_post_access'),
                                ['page_name']);
                        }
                    } else {
                        $parent_item = [
                            'TITLE' => tl('social_component_join_group',
                                $username, $group['GROUP_NAME']),
                            'DESCRIPTION' =>
                                tl('social_component_join_group_detail',
                                    date("r", $group['JOIN_DATE']),
                                    $group['GROUP_NAME']),
                            'ID' => -$group_id,
                            'PARENT_ID' => -$group_id,
                            'GROUP_ID' => $group_id
                        ];
                    }
                    $title = "-- " . $parent_item['TITLE'];
                    $id = $group_model->addGroupItem($parent_item["ID"],
                        $group_id, $user_id, $title, $description);
                    list($bots_called, $post_parts) =
                        $this->getRequestedBots($group_id, $description);
                    $result = $this->handleResourceUploads(
                            $group_id, "post" . $id);
                    if ($result == self::UPLOAD_FAILED) {
                        return $parent->redirectWithMessage(
                            tl('social_component_upload_error'));
                    }
                    $followers = $group_model->getThreadFollowers(
                        $parent_item["ID"], $group['OWNER_ID'], $user_id);
                    $server = new MailServer(C\MAIL_SENDER, C\MAIL_SERVER,
                        C\MAIL_SERVERPORT, C\MAIL_USERNAME, C\MAIL_PASSWORD,
                        C\MAIL_SECURITY);
                    $post_url = "";
                    if (in_array($group['REGISTER_TYPE'],
                        [C\PUBLIC_BROWSE_REQUEST_JOIN, C\PUBLIC_JOIN])) {
                        $post_url = B\feedsUrl("thread", $parent_item["ID"],
                            true, "group", false) . "preserve=true\n";
                    }
                    $subject = tl('social_component_thread_notification',
                        $parent_item['TITLE']);
                    $body = tl('social_component_notify_body') . "\n" .
                        $parent_item['TITLE']."\n".
                        $post_url .
                        tl('social_component_notify_closing')."\n".
                        tl('social_component_notify_signature');
                    foreach ($followers as $follower) {
                        if (empty($follower['USER_ID'])) {
                            continue;
                        }
                        if (!$user_model->isBotUser($follower['USER_ID'])) {
                            $message = tl('social_component_notify_salutation',
                                $follower['USER_NAME']) . "\n\n";
                            $message .= $body;
                            $server->send($subject, C\MAIL_SENDER,
                                $follower['EMAIL'], $message);
                        }
                    }
                    $this->addAnyBotResponses($parent_item["ID"], $group_id,
                        $bots_called, $title, $post_parts);
                    return $parent->redirectWithMessage(
                        tl('social_component_comment_added'), ['page_name']);
                    break;
                case "addcontact":
                    $data['CONTACT_ID'] = $contact_id;
                    return $this->addContact($user_id, $data);
                    break;
                case "addgroup":
                    if ($_SESSION['USER_ID'] == C\PUBLIC_USER_ID) {
                        return $parent->redirectWithMessage(
                            tl('social_component_public_cant_add'));
                    }
                    $register =
                        $group_model->getRegisterType($just_group_id);
                    if ($just_group_id > 0 && !empty($register)
                        && $register != C\INVITE_ONLY_JOIN) {
                        if ($register >= C\LOW_JOIN_FEE &&
                            !in_array(C\MONETIZATION_TYPE,
                            ['group_fees','fees_and_keywords'])) {
                            $register = C\INVITE_ONLY_JOIN;
                            return $parent->redirectWithMessage(
                                tl('social_component_groupname_cant_add'));
                        }
                        return
                            $this->addGroup($data, $just_group_id, $register);
                    } else {
                        return $parent->redirectWithMessage(
                            tl('social_component_groupname_cant_add'));
                    }
                    break;
                case "deletepost":
                    if (!empty($_REQUEST['page_type']) &&
                        $_REQUEST['page_type'] == "page_and_feedback") {
                        $_REQUEST['a'] = "wiki";
                    }
                    if (!isset($_REQUEST['post_id'])) {
                        return $parent->redirectWithMessage(
                            tl('social_component_delete_error'),
                            ['page_name']);
                        break;
                    }
                    $post_id = $parent->clean($_REQUEST['post_id'], "int");
                    $group_item = $group_model->getGroupItem($post_id);
                    $success = false;
                    if ($group_item) {
                        // this method checks if user can delete post
                        $success =
                            $group_model->deleteGroupItem($post_id, $user_id);
                    }
                    $search_array = [["parent_id", "=", $just_thread, ""]];
                    $item_count = $group_model->getGroupItemCount($search_array,
                        $user_id, -1);
                    if (!empty($_REQUEST['page_type']) &&
                         $_REQUEST['page_type'] == "page_and_feedback") {
                         unset($_REQUEST['just_thread']);
                         $_REQUEST['group_id'] = $group_item['GROUP_ID'];
                    }
                    if ($success) {
                        $group_model->deleteResources($group_item["GROUP_ID"],
                            "post" . $post_id);
                        if ($item_count == 0) {
                            unset($_REQUEST['just_thread']);
                        }
                        return $parent->redirectWithMessage(
                            tl('social_component_item_deleted'),
                            ['page_name']);
                    } else {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_item_deleted'),
                            ['page_name']);
                    }
                    break;
                case "downvote":
                    if (!isset($_REQUEST['group_id']) || !$_REQUEST['group_id']
                        ||!isset($_REQUEST['post_id']) ||
                        !$_REQUEST['post_id']) {
                        return $parent->redirectWithMessage(
                            tl('social_component_vote_error'));
                    }
                    $post_id = $parent->clean($_REQUEST['post_id'], "int");
                    $group_id = $parent->clean($_REQUEST['group_id'], "int");
                    $group = $group_model->getGroupById($group_id,
                        $user_id, true);
                    if (!$group || $user_id == C\PUBLIC_USER_ID
                        || (!in_array($group["VOTE_ACCESS"],
                        [C\UP_DOWN_VOTING_GROUP] ) ) ) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_vote_access'));
                    }
                    $post_item = $group_model->getGroupItem($post_id);
                    if (!$post_item || $post_item['GROUP_ID'] != $group_id) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_post_access'));
                    }
                    if ($group_model->alreadyVoted($user_id, $post_id)) {
                        return $parent->redirectWithMessage(
                            tl('social_component_already_voted'));
                    }
                    $group_model->voteDown($user_id, $post_id);
                    return $parent->redirectWithMessage(
                        tl('social_component_vote_recorded'));
                    break;
                case "newthread":
                    if (!isset($_REQUEST['group_id']) ||
                        !$_REQUEST['group_id']) {
                        return $parent->redirectWithMessage(
                            tl('social_component_comment_error'));
                    }
                    $group_id = $parent->clean($_REQUEST['group_id'], "int");
                    if (!$description || !$title) {
                        return $parent->redirectWithMessage(
                            tl('social_component_need_title_description'));
                    }
                    $group_id = $parent->clean($_REQUEST['group_id'], "int");
                    $group = $group_model->getGroupById($group_id,
                        $user_id, true);
                    $new_thread = [C\GROUP_READ_WRITE, C\GROUP_READ_WIKI];
                    if (!$group || $user_id == C\PUBLIC_USER_ID ||
                        ($group["OWNER_ID"] != $user_id &&
                        !in_array($group["MEMBER_ACCESS"], $new_thread) &&
                        $user_id != C\ROOT_ID)) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_post_access'));
                    }
                    $thread_id = $group_model->addGroupItem(0,
                        $group_id, $user_id, $title, $description);
                    list($bots_called, $post_parts) =
                        $this->getRequestedBots($group_id, $description);
                    $result = $this->handleResourceUploads(
                            $group_id, "post" . $thread_id);
                    if ($result == self::UPLOAD_FAILED) {
                        return $parent->redirectWithMessage(
                            tl('social_component_upload_error'));
                    }
                    if ($user_id == $group['OWNER_ID']) {
                        $followers = $group_model->getGroupUsers($group_id);
                    } else {
                        $owner_name = $user_model->getUsername(
                            $group['OWNER_ID']);
                        $follower = $user_model->getUser($owner_name);
                        $followers = [$follower];
                    }
                    $server = new MailServer(C\MAIL_SENDER, C\MAIL_SERVER,
                        C\MAIL_SERVERPORT, C\MAIL_USERNAME, C\MAIL_PASSWORD,
                        C\MAIL_SECURITY);
                    $subject = tl('social_component_new_thread_mail',
                        $group['GROUP_NAME']);
                    $post_url = B\feedsUrl("thread", $thread_id, true,
                        "group", false)."preserve=true\n";
                    $body = tl('social_component_new_thread_body',
                        $group['GROUP_NAME'])."\n".
                        "\"".$title."\"\n".
                        $post_url .
                        tl('social_component_notify_closing')."\n".
                        tl('social_component_notify_signature');
                    foreach ($followers as $follower) {
                        if ($follower['USER_ID'] != $user_id &&
                            ($user_id == $group['OWNER_ID'] ||
                             $follower['USER_ID'] == $group['OWNER_ID'])
                        && !$user_model->isBotUser($follower['USER_ID'])) {
                            $message = tl('social_component_notify_salutation',
                                $follower['USER_NAME'])."\n\n";
                            $message .= $body;
                            $server->send($subject, C\MAIL_SENDER,
                                $follower['EMAIL'], $message);
                        }
                    }
                    $this->addAnyBotResponses($thread_id, $group_id,
                        $bots_called, "--" . $title, $post_parts);
                    $thread_url = B\feedsUrl('thread', $thread_id) .
                        C\CSRF_TOKEN . "=" .
                        $parent->generateCSRFToken($_SESSION["USER_ID"]) ;
                    $_SESSION['DISPLAY_MESSAGE'] =
                        tl('social_component_thread_created');
                    //return $parent->redirectLocation($thread_url);
                    return $parent->redirectWithMessage(
                        tl('social_component_thread_created'));
                    break;
                case "status":
                    $data['REFRESH'] = "feedstatus";
                    if (!empty($_REQUEST['feed_time']))
                    $data['REFRESH_TIMESTAMP'] = $parent->clean(
                        $_REQUEST['feed_time'], "int");
                    break;
                case "updatepost":
                    if (!empty($_REQUEST['page_type']) &&
                         $_REQUEST['page_type'] == "page_and_feedback") {
                         $_REQUEST['a'] = "wiki";
                         unset($_REQUEST['limit']);
                         unset($_REQUEST['num']);
                    }
                    if (!isset($_REQUEST['post_id'])) {
                        return $parent->redirectWithMessage(
                            tl('social_component_comment_error'),
                            ['page_name']);
                    }
                    if (!$description || !$title) {
                        return $parent->redirectWithMessage(
                            tl('social_component_need_title_description'),
                            ['page_name']);
                    }
                    $post_id = $parent->clean($_REQUEST['post_id'], "int");
                    $action = "updatepost" . $post_id;
                    if (!$parent->checkCSRFTime(C\CSRF_TOKEN, $action)) {
                        return $parent->redirectWithMessage(
                            tl('social_component_post_edited_elsewhere'),
                            ['page_name']);
                    }
                    $items = $group_model->getGroupItems(0, 1,
                        [["post_id", "=", $post_id, ""]], $user_id);
                    if (isset($items[0])) {
                        $item = $items[0];
                    } else {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_update_access'),
                            ['page_name']);
                    }
                    $group_id = $item['GROUP_ID'];
                    $_REQUEST['group_id'] = $group_id;
                    $group = $group_model->getGroupById($group_id, $user_id,
                        true);
                    $update_thread = [C\GROUP_READ_WRITE, C\GROUP_READ_WIKI];
                    if ($post_id != $item['PARENT_ID'] && $post_id > 0) {
                        $update_thread[] = C\GROUP_READ_COMMENT;
                        $parent_items = $group_model->getGroupItems(0, 1,
                            [["post_id", "=", $item['PARENT_ID'], ""]],
                            $user_id);
                        if (!empty($parent_items[0])) {
                            $parent_item = $parent_items[0];
                            $title = "-- " . $parent_item['TITLE'];
                        }
                    }
                    if (!$group || $user_id == C\PUBLIC_USER_ID ||
                        ($group["OWNER_ID"] != $user_id &&
                        !in_array($group["MEMBER_ACCESS"], $update_thread) &&
                        $user_id != ROOT_ID)) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_update_access'),
                            ['page_name']);
                        break;
                    }
                    $group_model->updateGroupItem($post_id, $title,
                        $description);
                    $result = $this->handleResourceUploads(
                        $group_id, "post" . $post_id);
                    if ($result == self::UPLOAD_FAILED) {
                        return $parent->redirectWithMessage(
                            tl('social_component_upload_error'),
                            ['page_name']);
                    }
                    return $parent->redirectWithMessage(
                        tl('social_component_post_updated'),
                        ['page_name']);
                    break;
                case "upvote":
                    if (!isset($_REQUEST['group_id']) || !$_REQUEST['group_id']
                        ||!isset($_REQUEST['post_id']) ||
                        !$_REQUEST['post_id']) {
                        return $parent->redirectWithMessage(
                            tl('social_component_vote_error'));
                    }
                    $post_id = $parent->clean($_REQUEST['post_id'], "int");
                    $group_id = $parent->clean($_REQUEST['group_id'], "int");
                    $group = $group_model->getGroupById($group_id, $user_id,
                        true);
                    if (!$group || $user_id == C\PUBLIC_USER_ID ||
                        (!in_array($group["VOTE_ACCESS"],
                        [C\UP_VOTING_GROUP, C\UP_DOWN_VOTING_GROUP] ) ) ) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_vote_access'));
                    }
                    $post_item = $group_model->getGroupItem($post_id);
                    if (!$post_item || $post_item['GROUP_ID'] != $group_id) {
                        return $parent->redirectWithMessage(
                            tl('social_component_no_post_access'));
                    }
                    if ($group_model->alreadyVoted($user_id, $post_id)) {
                        return $parent->redirectWithMessage(
                            tl('social_component_already_voted'));
                    }
                    $group_model->voteUp($user_id, $post_id);
                    return $parent->redirectWithMessage(
                        tl('social_component_vote_recorded'));
                    break;
            }
        }
        $view_mode = (isset($_REQUEST['v'])) ?
            $parent->clean($_REQUEST['v'], "string") : "grouped";
        $data['VIEW_MODE'] = $view_mode;
        $view_mode = (!$just_group_id && !$just_user_id
            && !$just_thread) ? $view_mode : "ungrouped";
        if ($view_mode == "grouped") {
            if ($user_id == C\PUBLIC_USER_ID) {
                $_REQUEST = ['c' => "admin", 'a' => '', C\CSRF_TOKEN => ''];
                return $parent->redirectWithMessage(
                    tl("social_component_login_first"));
            }
            $this->initSocialBadges($user_id, $data);
            $this->calculateRecentFeedsAndThread($data, $user_id);
            return $this->calculateGroupedFeeds($user_id, $limit,
                $results_per_page, $controller_name, $data);
        } else {
            $this->initSocialBadges($user_id, $data);
        }
        $groups_count = 0;
        $pages = [];
        $page = [];
        if (!$just_user_id && (!$just_thread || $just_thread < 0)) {
            $search_array = [
                ["group_id", "=", max(-$just_thread, $just_group_id), ""],
                ["access", "!=", C\GROUP_PRIVATE, ""],
                ["status", "=", C\ACTIVE_STATUS, ""],
                ["join_date", "=", "", "DESC"]
            ];
            $groups = $group_model->getRows(
                0, $limit + $results_per_page, $groups_count,
                $search_array, [$user_id, false]);
            // create feed items for first join dates to given groups
            foreach ($groups as $group) {
                $page = [];
                $page['USER_ICON'] = C\SHORT_BASE_URL .
                    "resources/anonymous.png";
                $page[self::TITLE] = tl('social_component_join_group',
                    $username, $group['GROUP_NAME']);
                $page[self::DESCRIPTION] =
                    tl('social_component_join_group_detail',
                        date("r", $group['JOIN_DATE']), $group['GROUP_NAME']);
                $page['ID'] = -$group['GROUP_ID'];
                $page['PARENT_ID'] = -$group['GROUP_ID'];
                $page['USER_NAME'] = "";
                $page['USER_ID'] = "";
                $page['GROUP_ID'] = $group['GROUP_ID'];
                $page[self::SOURCE_NAME] = $group['GROUP_NAME'];
                $page['MEMBER_ACCESS'] = $group['MEMBER_ACCESS'];
                $page['STATUS'] = $group['STATUS'];
                if ($group['OWNER_ID'] == $user_id || $user_id == C\ROOT_ID) {
                    $page['MEMBER_ACCESS'] = C\GROUP_READ_WIKI;
                }
                $page['PUBDATE'] = $group['JOIN_DATE'];
                $pages[$group['JOIN_DATE']] = $page;
            }
        }
        $pub_clause = ['pub_date', "=", "", "DESC"];
        $sort = "krsort";
        if ($just_thread) {
            $thread_parent = $group_model->getGroupItem($just_thread);
            $group_id = $thread_parent['GROUP_ID'] ?? false;
            if (isset($thread_parent["TYPE"]) &&
                $thread_parent["TYPE"] == C\WIKI_GROUP_ITEM) {
                $page_info = $group_model->getPageInfoByThread($just_thread);
                if (isset($page_info["PAGE_NAME"])) {
                    $group_id = $page_info['GROUP_ID'];
                    $data["WIKI_PAGE_NAME"] = $page_info["PAGE_NAME"];
                    $group = $group_model->getGroupById($group_id, $user_id,
                        true);
                    if ($group["OWNER_ID"] == $user_id ||
                        ($group["STATUS"] == C\ACTIVE_STATUS &&
                        $group["MEMBER_ACCESS"] == C\GROUP_READ_WIKI) &&
                        $user_id != C\PUBLIC_USER_ID) {
                        $data["CAN_EDIT"] = true;
                        $edit_or_source = "edit";
                    } else {
                        $data["CAN_EDIT"] = false;
                        $edit_or_source = "source";
                    }
                }
            }
            if ((!isset($_REQUEST['f']) ||
                !in_array($_REQUEST['f'], ["rss", "json", "serial"]))) {
                $pub_clause = ['pub_date', "=", "", "ASC"];
                $sort = "ksort";
                $parent->model("impression")->add($user_id, $just_thread,
                    C\THREAD_IMPRESSION);
                $parent->model("impression")->add($user_id, $group_id,
                    C\GROUP_IMPRESSION);
            }
        }
        $search_array = [
            ["parent_id", "=", $just_thread, ""],
            ["group_id", "=", $just_group_id, ""],
            ["group_name", "NOT BEGINS WITH", C\PERSONAL_GROUP_PREFIX, ""],
            ["user_id", "=", $just_user_id, ""],
            $pub_clause];
        $for_group = ($just_group_id) ? $just_group_id : (($just_thread) ?
            -2 : -1);
        if (!empty($just_thread) ) {
            $data['JUST_THREAD'] = $just_thread;
        }
        list($item_count, $pages) = $this->initializeFeedItems($data, $pages,
            $user_id, $search_array, $for_group, $sort, $limit,
            $results_per_page);
        $data['SUBTITLE'] = "";
        $type = "";
        $type_id = "";
        if (!empty($just_thread) ) {
            $thread_start_item = $group_model->getGroupItem($just_thread);
            if (empty($thread_start_item) ||
                empty($thread_start_item["TITLE"])) {
                $data['NO_POSTS_IN_THREAD'] = true;
                if ($just_thread < 0) {
                    $data['SUBTITLE'] = empty($pages[0][self::TITLE]) ?
                        "" : $pages[0][self::TITLE];
                    $data["GROUP_ID"] = -$just_thread;
                    $group = $group_model->getGroupById(
                        $data["GROUP_ID"], $user_id);
                    if (!empty($group)) {
                        $data["GROUP_NAME"] = $group["GROUP_NAME"];
                        $data['GROUP_STATUS'] = $group['STATUS'];
                    } else {
                        $title = "";
                        $data['SUBTITLE'] = "";
                    }
                }
            } else {
                $title = $thread_start_item["TITLE"];
                $data['SUBTITLE'] = trim($title, "\- \t\n\r\0\x0B");
                $type = "thread";
                $type_id = $just_thread;
                $group = $group_model->getGroupById(
                    $thread_start_item['GROUP_ID'], $user_id);
                $data["GROUP_ID"] = $thread_start_item['GROUP_ID'];
                if (!empty($group)) {
                    $data["GROUP_NAME"] = $group["GROUP_NAME"];
                    $data['GROUP_STATUS'] = $group['STATUS'];
                } else {
                    $title = "";
                    $data['SUBTITLE'] = "";
                    $data['NO_POSTS_IN_THREAD'] = true;
                }
            }
        }
        if (!$just_group_id && !$just_thread) {
           $data['GROUP_STATUS'] = C\ACTIVE_STATUS;
        }
        if ($just_group_id) {
            $group = $group_model->getGroupById($just_group_id, $user_id);
            if (!$group) {
                if ($user_id == C\PUBLIC_USER_ID) {
                    $_REQUEST = ['c' => "admin", 'a' => '', C\CSRF_TOKEN => ''];
                    return $parent->redirectWithMessage(
                        tl("social_component_login_first"));
                }
                unset($_REQUEST['route']);
                $_REQUEST['just_group_id'] = C\PUBLIC_GROUP_ID;
                return $parent->redirectWithMessage(
                    tl("social_component_no_group_access"), false, false,
                    true);
            }
            $data['GROUP_STATUS'] = $group['STATUS'];
            if (!isset($page[self::SOURCE_NAME]) ) {
                $page[self::SOURCE_NAME] = $group['GROUP_NAME'];
            }
            if (empty($pages) ) {
                $data['NO_POSTS_YET'] = true;
                if ($user_id == $group['OWNER_ID'] || $user_id == C\ROOT_ID) {
                        // this case happens when a group is no read
                        $data['NO_POSTS_START_THREAD'] = true;
                }
            }
            if ($user_id != C\PUBLIC_USER_ID &&
                !$group_model->checkUserGroup($user_id, $just_group_id)) {
                $data['SUBSCRIBE_LINK'] = $group_model->getRegisterType(
                    $just_group_id);
            }
            $data['SUBTITLE'] = $page[self::SOURCE_NAME];
            $type= "group";
            $type_id = $just_group_id;
            $data['JUST_GROUP_ID'] = $just_group_id;
            $parent->model("impression")->add($user_id, $just_group_id,
                C\GROUP_IMPRESSION);
        }
        if ($just_user_id) {
            $page["USER_NAME"] = $user_model->getUsername($just_user_id);
            $data['SUBTITLE'] = $page["USER_NAME"];
            $type = "user";
            $type_id = $just_user_id;
            $data['JUST_USER_ID'] = $just_user_id;
        }
        if ($user_id != C\PUBLIC_USER_ID) {
            $thread_ids = $parent->model("impression")->recent($user_id,
                C\THREAD_IMPRESSION, 3);
        }
        $this->calculateRecentFeedsAndThread($data, $user_id);
        $data['TOTAL_ROWS'] = $item_count + $groups_count;
        $data['RESULTS_PER_PAGE'] = $results_per_page;
        $token_string = ($user_id !=  C\PUBLIC_USER_ID )? C\CSRF_TOKEN . "=".
            $this->parent->generateCSRFToken($user_id) : "";
        $data['PAGING_QUERY'] = htmlentities(B\feedsUrl($type, $type_id,
            true, $controller_name));
        $paging_query = $data['PAGING_QUERY'];
        if (!empty($type)) {
            $data['RSS_FEED_URL'] = $paging_query . "f=rss";
        }
        if ($view_mode == 'ungrouped') {
            $connector = (substr($paging_query, -1) == "?") ? "" :
                "&amp;";
            $paging_query .= "{$connector}v=ungrouped";
            $data['RSS_FEED_URL'] = $paging_query . "&amp;f=rss";
        }
        $paging_query = html_entity_decode($paging_query);
        $data['SCRIPT'] .= " let nextPage = initNextResultsPage($limit," .
            " {$data['TOTAL_ROWS']}, $results_per_page, ".
            "'$paging_query&$token_string', '', 'results-container', ".
            "'result-batch');\n";
        if ($limit > 0) {
            $data['SCRIPT'] .= " let previousPage = initPreviousResultsPage(".
                "$limit, {$data['TOTAL_ROWS']}, $results_per_page, ".
                "'$paging_query&$token_string', 'results-container', ".
                "'result-batch');\n";
        }
        if (!empty($data['REFRESH_TIMESTAMP']) && !empty($pages)) {
            $max_pubdate = 0;
            $num_pages = count($pages);
            for ($i = 0; $i < $num_pages; $i++) {
                $page = $pages[$i];
                if (empty($page['PUBDATE']) ||
                    ($page['PUBDATE'] <= $data['REFRESH_TIMESTAMP'] &&
                    !empty($page['EDIT_DATE']) &&
                    $page['EDIT_DATE'] > $max_pubdate)) {
                    unset($pages[$i]);
                    $limit++;
                    continue;
                }
                if ($page['PUBDATE'] > $max_pubdate) {
                    $max_pubdate = $page['PUBDATE'];
                }
                if (!empty($page['EDIT_DATE']) &&
                    $page['EDIT_DATE'] > $max_pubdate) {
                    $max_pubdate = $page['EDIT_DATE'];
                }
            }
            if ($max_pubdate <= $data['REFRESH_TIMESTAMP']) {
                \seekquarry\yioop\library\webExit();
            }
        }
        $data['PAGES'] = array_values($pages);
        $data['LIMIT'] = $limit;
        $this->initializeWikiEditor($data, -1);
        return $data;
    }
    /**
     * Handles requests for the user messages subsystem of yioop that allows
     * users to directly send messages to each other. This involves for a user
     * request gettting a list of user contacts, getttingmessages of the
     * currently selected user and handles any new messages posted.
     * This data is the sent to UsermessagesElement for display.
     */
    public function userMessages()
    {
        $parent = $this->parent;
        $controller_name = (get_class($parent) == C\NS_CONTROLLERS .
            "AdminController") ? "admin" : "group";
        $data["CONTROLLER"] = $controller_name;
        $data['VIEW_MODE'] = "ungrouped";
        $data['SUBTITLE'] = C\PERSONAL_GROUP_PREFIX;
        $group_model = $parent->model("group");
        $user_model = $parent->model("user");
        $data["ELEMENT"] = "usermessages";
        $data['SCRIPT'] = "";
        $data["INCLUDE_STYLES"] = ["messages", "editor"];
        $data["INCLUDE_SCRIPTS"] = ["messages"];
        if (!isset($_SESSION['USER_ID'])) {
            $_REQUEST = ['c' => "admin", 'a' => '', C\CSRF_TOKEN => ''];
            return $parent->redirectWithMessage(
                tl("social_component_login_first"));
        }
        $user_id = $_SESSION['USER_ID'];
        $username = $user_model->getUsername($user_id);
        if (isset($_REQUEST['num'])) {
            $results_per_page = $parent->clean($_REQUEST['num'], "int");
        } else if (isset($_SESSION['MAX_PAGES_TO_SHOW']) &&
            $_SESSION['MAX_PAGES_TO_SHOW'] > 0) {
            $results_per_page = $_SESSION['MAX_PAGES_TO_SHOW'];
        } else {
            $results_per_page = C\NUM_RESULTS_PER_PAGE;
        }
        if (isset($_REQUEST['limit'])) {
            $limit = $parent->clean($_REQUEST['limit'], "int");
        } else {
            $limit = 0;
        }
        $contact_id = $parent->clean($_REQUEST["contact_id"]
            ?? "", 'int');
        $contact_filter = $parent->clean($_REQUEST['contact_name'] ?? "",
            "string");
        $data['USER_ID'] = $user_id;
        $data['CONTACT_ID'] = $contact_id;
        $data['CONTACT_NAME'] = (!empty($contact_id)) ?
            $user_model->getUsername($contact_id) : "";
        $data['CONTACT_FILTER'] = $contact_filter;
        $data['CONTACT_ICON_URL'] = $user_model->getUserIconUrl($contact_id);
        $user_messages_id = $group_model->getPersonalGroupId($user_id);
        $data['USER_MESSAGES_ID'] = $user_messages_id;
        $message_actions = ['addcontact' => 'addContact',
            'blockcontact' => 'blockContact',
            'ignorecontact' => 'ignoreContact',
            'newmessage' => 'newMessage', "status" =>
            "messagesStatus"];
        if (in_array($_REQUEST['arg'] ?? "", array_keys($message_actions))) {
            $action = $message_actions[$_REQUEST['arg']];
            if (!empty($contact_filter)) {
                $data['CONTACT_ID'] = $user_model->getUserId($contact_filter);
                $data['CONTACT_NAME'] = $contact_filter;
                $data['CONTACT_FILTER'] = "";
            }
            return $this->$action($user_id, $data);
        }
        $data[C\CSRF_TOKEN] =
            $parent->generateCSRFToken($user_id);
        $messages_url = B\feedsUrl('user_messages',
            $data['CONTACT_ID'], true, $data['CONTROLLER']) .
            C\CSRF_TOKEN . "=" . $data[C\CSRF_TOKEN];
        $data['SCRIPT'] .= 'window.start_url = "'. $messages_url .
            '&arg=status&conversation_time=";';
        $data['SCRIPT'] .=  "window.tl = {".
            'social_component_no_longer_update:"'.
                tl('social_component_no_longer_update').'"'.
            '}; doUpdate();';
        $contact_ids = array_diff($group_model->getGroupUserIds(
            $user_messages_id), [$user_id]);
        $contacts = $this->marshallContactInfo($user_id, $contact_ids,
            $contact_filter);
        $requests_thread_id = $group_model->getGroupThreadId(
            $user_messages_id, null,  $group_model->getMessagesThreadTitle(
                [$user_id]));
        $contact_requests = [];
        if (!empty($requests_thread_id)) {
            $pre_contact_requests = $group_model->getThreadFollowers(
                $requests_thread_id);
            $contact_requests = [];
            foreach ($pre_contact_requests as $pre_contact_request) {
                if (!empty($contact_name) && strpos(
                    $pre_contact_request['USER_NAME'],
                    $contact_filter) === false) {
                    continue;
                }
                $pre_contact_request_id = $pre_contact_request['USER_ID'];
                $pre_contact_request['ICON_URL'] =
                    $user_model->getUserIconUrl($pre_contact_request_id);
                $contact_requests[$pre_contact_request_id] =
                    $pre_contact_request;
            }
        }
        $data['CONTACTS'] = $contacts;
        $data['CONTACT_REQUESTS'] = $contact_requests;
        $data['ELEMENT'] = 'usermessages';
        $data['MESSAGES'] = [];
        if (!empty($contact_id)) {
            $message_thread_id = $group_model->getGroupThreadId(
                $user_messages_id, $user_id,
                    $group_model->getMessagesThreadTitle(
                    [$user_id, $contact_id]));
            if ($message_thread_id) {
                $parent->model("impression")->add($user_id, $message_thread_id,
                    C\THREAD_IMPRESSION);
                $search_array = [
                 ["parent_id", "=", $message_thread_id, ""],
                 ["group_id", "=", $user_messages_id, ""],
                 ["pubdate", "", "", "DESC"]];
                list(, $data['MESSAGES']) = $this->initializeFeedItems($data,
                    [], $user_id, $search_array, -2, "krsort", $limit,
                    100);
            }
        }
        $this->initSocialBadges($user_id, $data);
        $this->initializeWikiEditor($data, -1);;
        return $data;
    }
    /**
     * Gets an array of contact details including USER_NAME, ICON_URL,
     * NUM_UNREAD_MESSAGES for
     * an array of contact ids for $user_id, subject to a filter.
     * @param array $contact_ids id's that contact details are desired of
     * @param string $contact_filter a substring of user_names to restrict
     *  the returned details to (i.e., only contacts whose user name's match
     *  this substring)
     * @return array $contacts array of contact details (which in turn
     *  is an array with fields USER_NAME, ICON_URL, ...) for ids with
     *  user names matching the filter
     */
    private function marshallContactInfo($user_id, $contact_ids,
        $contact_filter = "")
    {
        $parent = $this->parent;
        $user_model = $parent->model("user");
        $group_model = $parent->model("group");
        $impression_model = $parent->model("impression");
        $contacts= [];
        $personal_group_id = $group_model->getPersonalGroupId($user_id);
        foreach ($contact_ids as $contact_id) {
            $contact_username = $user_model->getUsername(
                $contact_id);
            if (!empty($contact_filter) && strpos($contact_username,
                $contact_filter) === false) {
                continue;
            }
            $icon_url = $user_model->getUserIconUrl($contact_id);
            $chat_id = $group_model->getGroupThreadId(
                $personal_group_id, $user_id,
                $group_model->getMessagesThreadTitle(
                [$user_id, $contact_id]));
            if (empty($chat_id)) {
                continue;
            }
            $chat_stamp = $impression_model->mostRecentThreadView($user_id,
                $chat_id);
            if (empty($chat_stamp)) {
                $chat_stamp = 0;
            }
            $contacts[$contact_id] = [
                "USER_NAME" => $contact_username,
                "ICON_URL" => $icon_url,
                "NUM_UNREAD_MESSAGES" => floor($group_model->getThreadPostCount(
                    $chat_id, $chat_stamp)/2)
                ];
        }
        return $contacts;
    }
    /**
     * Get messages since $_REQUEST['conversation_time'] for
     * the conversation betwene $user_id and $data['CONTACT_ID'].
     * Add them to a list in $data['MESSAGES']
     * @param int $user_id user we are trying to update conversation of
     * @param array $data this will be data sent to the view, but should
     *  be called after the 'CONTACT_ID' field has been set to the value
     *  of the person who $user_id is  have a messaging exchange
     * @return array $data updated view data with new messages (if any)
     */
    private function messagesStatus($user_id, $data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $data["REFRESH"] = "feedstatus";
        $data['MESSAGES'] = [];
        $message_thread_id = $group_model->getGroupThreadId(
            $data['USER_MESSAGES_ID'], $user_id,
            $group_model->getMessagesThreadTitle(
            [$user_id, $data["CONTACT_ID"]]));
        $conversation_time = $parent->clean($_REQUEST['conversation_time'],
            "int") ?? 0;
        if (!$message_thread_id || !$conversation_time) {
            return $data;
        }
        $search_array = [
         ["parent_id", "=", $message_thread_id, ""],
         ["group_id", "=", $data['USER_MESSAGES_ID'], ""],
         ["pubdate", ">", $conversation_time, "DESC"]];
        list(, $data['MESSAGES']) = $this->initializeFeedItems($data,
            [], $user_id, $search_array, -2, "krsort", $limit,
            100);
        return $data;
    }
    /**
     * Contains the logic need to add a contact $data['CONTACT_ID'] to
     * the list of $user_id's contacts for messaging.
     *
     * @param int $user_id id of user adding contact to
     * @param array $data current data to be sent to view after processing
     *  needs to contain a field $data['CONTACT_ID'] with the contact_id
     *  of user to add to contacts
     */
    private function addContact($user_id, $data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $user_model = $parent->model("user");
        $contact_id = $data['CONTACT_ID'] ?? "";
        if (empty($contact_id)) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_contact'));
        }
        if ($user_id == C\PUBLIC_USER_ID || $user_id == $contact_id) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_user'));
        }
        $user_messages_id = $group_model->getPersonalGroupId($user_id);
        $contact_messages_id = $group_model->getPersonalGroupId($contact_id);
        if (!empty($contact_id) &&
            $user_model->getUserStatus($contact_id) !=
            C\ACTIVE_STATUS) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_contact'));
        }
        $group_model->addUserGroup($contact_id, $user_messages_id);
        $messages_thread_title = $group_model->getMessagesThreadTitle(
            [$user_id, $contact_id]);
        $_REQUEST["contact_id"] = $contact_id;
        if ($group_model->isUserIdInContacts($user_id,
            $contact_id)) {
            $existing_parent_id =
                $group_model->getGroupThreadId(
                $contact_messages_id, $contact_id,
                $messages_thread_title);
            $group_model->addGroupItem(
                $existing_parent_id, $user_messages_id, $user_id,
                $messages_thread_title, "");
            $request_thread_title = $group_model->getMessagesThreadTitle(
                [$user_id]);
            $request_thread_id = $group_model->getGroupThreadId(
                $user_messages_id, $contact_id, $request_thread_title);
            if (!empty($request_thread_id)) {
                $group_model->deleteGroupItem($request_thread_id, $contact_id);
            }
            return $parent->redirectWithMessage(
                tl('social_component_connection_established'), ["contact_id"]);
        } else {
            $group_model->addGroupItem(0,
                $user_messages_id, $user_id, $messages_thread_title,
                "");
            $blocked_thread_title = $group_model->getMessagesThreadTitle(
                [$user_id, "blocked"]);
            $blocked_thread_id = $group_model->getGroupThreadId(
                $user_messages_id, $contact_id, $blocked_thread_title);
            if (!$blocked_thread_id) {
                $request_thread_title = $group_model->getMessagesThreadTitle(
                    [$contact_id]);
            }
            // thread used to keep tract of contact requests for $contact_id
            $group_model->addGroupItem(0,
                $contact_messages_id, $user_id, $request_thread_title,
                "");
            return $parent->redirectWithMessage(
                tl('social_component_connection_requested'), ["contact_id"]);
        }
    }
    /**
     * When a contact request is made the receiving user can either accept,
     * ignore, or block. Accept means connection is made, ignore means
     * the connection request is removed from the list of request, but the
     * requestor could send a new request, and block means that a new request
     * will automatically be discarded. This method implements the ignore
     * connection request for a user with id $user_id from a user
     * $data['CONTACT_ID'].
     *
     * @param int $user_id id of user who is doing the ignoring
     * @param array $data current data to be sent to view after processing
     *  needs to contain a field $data['CONTACT_ID'] with the contact_id
     *  of user to ignore
     */
    private function ignoreContact($user_id, $data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $user_model = $parent->model("user");
        $contact_id = $data['CONTACT_ID'] ?? "";
        if (empty($contact_id)) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_contact'));
        }
        if ($user_id == C\PUBLIC_USER_ID || $user_id == $contact_id) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_user'));
        }
        $user_messages_id = $group_model->getPersonalGroupId($user_id);
        if (!empty($contact_id) &&
            $user_model->getUserStatus($contact_id) !=
            C\ACTIVE_STATUS) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_contact'));
        }
        $request_thread_title = $group_model->getMessagesThreadTitle(
            [$user_id]);
        $request_thread_id = $group_model->getGroupThreadId(
            $user_messages_id, $contact_id, $request_thread_title);
        if (!empty($request_thread_id)) {
            $group_model->deleteGroupItem($request_thread_id, $contact_id);
        }
        return $parent->redirectWithMessage(
            tl('social_component_connection_request_ignored'));
    }
    /**
     * When a contact request is made the receiving user can either accept,
     * ignore, or block. Accept means connection is made, ignore means
     * the connection request is removed from the list of request, but the
     * requestor could send a new request, and block means that a new request
     * will automatically be discarded. This method implements the block
     * connection request for a user with id $user_id from a user
     * $data['CONTACT_ID'].
     *
     * @param int $user_id id of user who is doing the blocking
     * @param array $data current data to be sent to view after processing
     *  needs to contain a field $data['CONTACT_ID'] with the contact_id
     *  of user to block
     */
    private function blockContact($user_id, $data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $user_model = $parent->model("user");
        $contact_id = $data['CONTACT_ID'] ?? "";
        if (empty($contact_id)) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_contact'));
        }
        if ($user_id == C\PUBLIC_USER_ID || $user_id == $contact_id) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_user'));
        }
        $user_messages_id = $group_model->getPersonalGroupId($user_id);
        if (!empty($contact_id) &&
            $user_model->getUserStatus($contact_id) !=
            C\ACTIVE_STATUS) {
            return $parent->redirectWithMessage(
                tl('social_component_invalid_contact'));
        }
        $contact_messages_id = $group_model->getPersonalGroupId($contact_id);
        $request_thread_title = $group_model->getMessagesThreadTitle(
            [$user_id]);
        $request_thread_id = $group_model->getGroupThreadId(
            $user_messages_id, $contact_id, $request_thread_title);
        if (!empty($request_thread_id)) {
            $group_model->deleteGroupItem($request_thread_id, $contact_id);
            $block_thread_title = $group_model->getMessagesThreadTitle(
                [$contact_id, "blocked"]);
            $group_model->addGroupItem(0,
                $contact_messages_id, $user_id, $block_thread_title,
                "");
        }
        return $parent->redirectWithMessage(
            tl('social_component_connection_request_blocked'));
    }
    /**
     * Sends a message cleaned $_REQUEST["description"] from user with id
     * $user_id to user with $data['CONTACT_ID']
     *
     * Expects a message $_REQUEST["description"] coming from message form to
     * to send.
     *
     * @param int $user_id id of user who is sending the message
     * @param array $data current data to be sent to view after processing
     *  needs to contain a field $data['CONTACT_ID'] with the contact_id
     *  of user to send to
     */
    private function newMessage($user_id, $data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $user_messages_id = $group_model->getPersonalGroupId($user_id);
        $messages_thread_title = $group_model->getMessagesThreadTitle(
            [$user_id, $data['CONTACT_ID']]);
        $description = $contact_id = $parent->clean($_REQUEST["description"]
            ?? "", 'string');
        $message_thread_id = $group_model->getGroupThreadId($user_messages_id,
            $user_id, $messages_thread_title);
        $parent->model("impression")->add($user_id, $message_thread_id,
            C\THREAD_IMPRESSION);
        if (empty($message_thread_id)) {
            return $parent->redirectWithMessage(
                tl("social_component_invalid_message_thread"),
                ['contact_id']);
        }
        $thread_contributors = explode("-", $messages_thread_title);
        $first_group_id = -1;
        $result = self::UPLOAD_NO_FILES;
        foreach ($thread_contributors as $contributor_id) {
            $contributor_personal_id = $group_model->
                getPersonalGroupId($contributor_id);
            if (empty($contributor_personal_id)) {
                continue;
            }
            $message_id = $group_model->addGroupItem($message_thread_id,
                $contributor_personal_id, $user_id,
                "--" . $messages_thread_title, $description);
            if ($first_group_id == -1) {
                $first_group_id = $contributor_personal_id;
                $first_message_id = $message_id;
                $result = $this->handleResourceUploads($contributor_personal_id,
                    "post" . $message_id);
                if ($result == self::UPLOAD_FAILED) {
                    return $parent->redirectWithMessage(
                        tl('social_component_upload_error'));
                }
            } else if ($result == self::UPLOAD_SUCCESS) {
                $group_model->linkResourceFolders($contributor_personal_id,
                    "post" . $message_id, $first_group_id,
                    "post" . $first_message_id);
            }
        }
        return $parent->redirectWithMessage("", ['contact_id']);
    }
    /**
     * Determines a list of posts that might need to reply to a post in
     * a group
     *
     * @param int $group_id get chat bots following this group
     * @param string $description post message to see if called any bots
     *      by using a phrase like: @bot_name some request
     * @return array [array of bots referred to in post, array of post
     *      portions for each robot]
     */
    private function getRequestedBots($group_id, $description)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $bot_followers = $group_model->getGroupBots($group_id);
        $bots = [];
        $bots_called = [];
        $post_parts = [];
        foreach ($bot_followers as $bot_follower) {
            $bots[] = $bot_follower['USER_NAME'];
        }
        if (preg_match_all('/(?<!\w)@(\w+)\s([^@]*)/si', $description,
            $matches)) {
            foreach ($matches[1] as $match) {
                $match = mb_strtolower($match);
                $index = array_search($match, $bots);
                if ($index !== false) {
                    $bots_called[] = $bot_followers[$index];
                } else {
                    $bots_called[] = null;
                }
            }
            $post_parts = $matches[2];
        }
        return [$bots_called, $post_parts];
    }
    /**
     * This follows up to the thread post $thread_id to $group_id any
     * response that $bots following this group might have
     *
     * @param int $thread_id id of the thread post to follow up
     * @param int $group_id of group thread post was posted to
     * @param array $bots list of chat bot users following group
     * @param string $title title of thread post to follow up
     * @param array $posts for each bot the contents of message applicable
     *      to that bot
     */
    private function addAnyBotResponses($thread_id, $group_id, $bots, $title,
        $posts)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $num_bots = count($bots);
        $sites = [];
        $post_data = [];
        $time = time();
        $user_id = (empty($_SESSION['USER_ID'])) ? C\PUBLIC_USER_ID:
            $_SESSION['USER_ID'];
        $user_name = (empty($_SESSION['USER_NAME'])) ? "PUBLIC" :
            $_SESSION['USER_NAME'];
        if (empty($_SESSION["CHAT_BOT_STATES"])) {
            $_SESSION["CHAT_BOT_STATES"] = [];
        }
        for ($i = 0; $i < $num_bots; $i++) {
            if (empty($bots[$i]['USER_ID'])) {
                continue;
            }
            $bot_id = $bots[$i]['USER_ID'];
            if (empty($_SESSION["CHAT_BOT_STATES"][$bot_id])) {
                $_SESSION["CHAT_BOT_STATES"][$bot_id] = "0";
            }
            $bots[$i]['PATTERN'] =
                $this->computeBotPattern($bot_id, $posts[$i]);
            if (!empty($bots[$i]['PATTERN'])) {
                $bots[$i]['PATTERN']['VARS']['REMOTE_MESSAGE'] =
                    $this->interpolateBotVariables(
                    $bots[$i]['PATTERN']['REMOTE_MESSAGE'],
                    $bots[$i]['PATTERN']['VARS']);
            }
            if (empty($bots[$i]['PATTERN']['VARS']['REMOTE_MESSAGE'])) {
                $sites[$i] = [];
            } else {
                $sites[$i][CrawlConstants::URL] = $bots[$i]['CALLBACK_URL'];
                $post_data[$i] = "remote_message=".
                    urlencode($bots[$i]['PATTERN']['VARS']['REMOTE_MESSAGE']) .
                    "&post=" . urlencode($posts[$i]) . "&bot_token=" .
                    hash("sha256", $bots[$i]['BOT_TOKEN'] .
                        $time . $posts[$i]) . "*" . $time .
                    "&bot_name=" . $bots[$i]['USER_NAME'];
            }
        }
        $outputs = [];
        if (count($sites) > 0) {
            $outputs = FetchUrl::getPages($sites, false, 0, null,
                self::URL, self::PAGE, true, $post_data);
        }
        for ($i = 0;  $i < $num_bots; $i++) {
            if (!empty($bots[$i]['PATTERN']) &&
                isset($outputs[$i][self::PAGE]) ) {
                $bots[$i]['PATTERN']['VARS']['REMOTE_RESPONSE'] =
                    $outputs[$i][self::PAGE];
            }
        }
        foreach ($bots as $bot) {
            if (empty($bot['PATTERN'])) {
                continue;
            }
            $bot_id = $bot['USER_ID'];
            $result_state = $this->interpolateBotVariables(
                $bot['PATTERN']['RESULT_STATE'],
                $bot['PATTERN']['VARS']);
            $_SESSION["CHAT_BOT_STATES"][$bot_id] = (empty($result_state)) ?
                "0" : $result_state;
            $bot['PATTERN']['VARS']['RESULT_STATE'] = $result_state;
            $response = $this->interpolateBotVariables(
                $bot['PATTERN']['RESPONSE'],
                $bot['PATTERN']['VARS']);
            if (!empty($response)) {
                $group_model->addGroupItem($thread_id,
                    $group_id, $bot_id, $title, $response);
            }
        }
    }
    /**
     * Determines which, if any, chat bot patterns of chat bot $bot_id are
     * applicable to the post $post given the current state of the chat bot
     * for the user who made $post.
     *
     * @param int $bot_id of chat bot to look for applicable pattern
     * @param string $post messages to compare against pattern request
     *      expressions
     * @return array $pattern first pattern that matches. Its ['VARS'] field
     *      will contain any binding values that were made to make the match
     */
    private function computeBotPattern($bot_id, $post)
    {
        $parent = $this->parent;
        $bot_model = $parent->model("bot");
        $total = 0;
        $patterns = $bot_model->getRows(0, C\MAX_BOT_PATTERNS,
            $total, [], [$bot_id]);
        if (empty($patterns)) {
            return [];
        }
        $post = preg_replace("/" . C\PUNCT . "/", " ", $post);
        $post = trim(preg_replace("/\s+/mu", " ", $post));
        foreach ($patterns as $pattern) {
            $request = $pattern['REQUEST'];
            $num_vars = preg_match_all('/\$(\w+)/', $request, $var_matches);
            $request = preg_replace('/\$\w+/', "dzqqzd", $request);
            $request = preg_replace("/" . C\PUNCT . "/", " ", $request);
            $request = trim(preg_replace('/\s+/mu', " ", $request));
            $request = preg_quote($request, "/");
            $request = preg_replace('/dzqqzd/', "(.+)", $request);
            $num_matches = preg_match("/$request/iu", $post, $matches);
            if ($num_matches > 0) {
                array_shift($matches);
                $bot_variables = array_combine($var_matches[1], $matches);
                if (!empty($_SESSION['USER_NAME'])) {
                    $bot_variables['USER_NAME'] = $_SESSION['USER_NAME'];
                }
                $state = $this->interpolateBotVariables(
                    $pattern['TRIGGER_STATE'], $bot_variables);
                if ($_SESSION["CHAT_BOT_STATES"][$bot_id] == $state) {
                    $pattern['VARS'] = $bot_variables;
                    return $pattern;
                }
            }
        }
        return [];
    }
    /**
     * Given a string $to_interpolate with variables in it (strings of word
     * characters beginning with a $) and given an array of variable =>
     * value, replaces the variables in $to_inpolate with their corresponding
     * value, returning the resulting string
     *
     * @param string $to_interpolate string to replace variables in
     * @param array $bot_variables sequence of variable => value pairs to
     *      replace in string.
     * @return string $to_interpolate after substitutions have been made
     */
    private function interpolateBotVariables($to_interpolate, $bot_variables)
    {
        foreach ($bot_variables as $var => $value) {
            $pattern = '/\$' . preg_quote($var, "/") . '/u';
            $to_interpolate = preg_replace($pattern, $value, $to_interpolate);
        }
        return $to_interpolate;
    }
    /**
     * Used to compute set up a list of feed items to be displayed by the
     * groupFeeds activity
     *
     * @param array &$data associative array of values to be echoed by the view
     *      this method might add to INCLUDE_SCRIPT formatting scripts such as
     *      for math which might be used to help draw feed items
     * @param array $pages contains feed items corresponding to first join
     *      dates to various groups. Other feed items will be added to this
     *      array
     * @param int $user_id id of user requesting thread info
     * @param array $search_array associative array used to determine where
     *      clause of what threads, groups, or user posts to get feed items for
     * @param int $for_group if this value is set it is a assumed
     *     that group_items are being returned for only one group
     *     and that they should be grouped by thread
     * @param string $sort either ksort or krsort to specify final sort
     *      direction of feed items
     * @param int $limit index of first feed item out of all applicable items
     *      to display
     * @param int $results_per_page number of feed items to display feed data
     *      for
     */
    private function initializeFeedItems(&$data, $pages, $user_id,
        $search_array, $for_group, $sort, &$limit, $results_per_page)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $impression_model = $parent->model("impression");
        $user_model = $parent->model("user");
        $item_count = $group_model->getGroupItemCount($search_array, $user_id,
            $for_group);
        $updatable = false;
        if (!empty($data["JUST_THREAD"]) && $data["JUST_THREAD"] >= 0
            && empty($data["GROUP_ID"])) {
            $display_message = $_SESSION['DISPLAY_MESSAGE'] ?? "";
            $is_wiki = !empty($data["HEAD"]['page_type']) &&
                $data["HEAD"]['page_type'] == 'page_and_feedback';
            if (!$is_wiki &&
                $display_message == tl('social_component_comment_added')) {
                $limit = floor($item_count / $results_per_page) *
                    $results_per_page;
            }
            if ($limit > $item_count - $results_per_page) {
                $updatable = true;
            }
        }
        $group_items = $group_model->getGroupItems(0,
            $limit + $results_per_page, $search_array, $user_id, $for_group);
        $recent_found = false;
        $time = time();
        $j = 0;
        $parser = new WikiParser("", [], true);
        $locale_tag = L\getLocaleTag();
        $page = false;
        $math = false;
        $csrf_token = C\CSRF_TOKEN . "=" . $this->parent->generateCSRFToken(
            $user_id);
        foreach ($group_items as $item) {
            $page = $item;
            if (C\DIFFERENTIAL_PRIVACY && !empty($page['NUM_VIEWS'])) {
                /* Recalculate fuzzy view only if NUM_VIEWS
                   has been updated since last calculation
                 */
                if (empty($page['TMP_NUM_VIEWS']) ||
                    ($page['NUM_VIEWS'] != $page['TMP_NUM_VIEWS'])) {
                    // fuzzify the number of views to add privacy
                    $fuzzy_views =
                        $parent->addDifferentialPrivacy($page['NUM_VIEWS']);
                    $impression_model->updatePrivacyViews($page['ID'],
                        $page['NUM_VIEWS'], $fuzzy_views);
                    $page['NUM_VIEWS'] = $fuzzy_views;
                } else {
                    $page['NUM_VIEWS'] = $page['FUZZY_NUM_VIEWS'];
                }
            }
            if (empty($page['NUM_VIEWS'])) {
                $page['NUM_VIEWS'] = 0;
            }
            $page['USER_ICON'] = $user_model->getUserIconUrl($page['USER_ID']);
            $page[self::TITLE] = $page['TITLE'];
            unset($page['TITLE']);
            $description = $page['DESCRIPTION'];
            //start code for sharing crawl mixes
            preg_match_all("/\[\[([^\:\n]+)\:mix(\d+)\]\]/", $description,
                $matches);
            $num_matches = count($matches[0]);
            for ($i = 0; $i < $num_matches; $i++) {
                $match = preg_quote($matches[0][$i], "@");
                $match = str_replace("@","\@", $match);
                $replace = "<a href='?c=admin&amp;a=mixCrawls" .
                    "&amp;arg=importmix&amp;".C\CSRF_TOKEN."=".
                    $parent->generateCSRFToken($user_id).
                    "&amp;timestamp={$matches[2][$i]}'>".
                    $matches[1][$i]."</a>";
                $description = preg_replace("@".$match."@u", $replace,
                    $description);
                $page["NO_EDIT"] = true;
            }
            //end code for sharing crawl mixes
            $page[self::DESCRIPTION] = $parser->parse($description);
            $page[self::DESCRIPTION] =
                $group_model->insertResourcesParsePage($item['GROUP_ID'],
                 "post" . $item['ID'], $locale_tag, $page[self::DESCRIPTION]);
            $page[self::DESCRIPTION] = preg_replace('/\[{rtoken}\]/',
                $csrf_token, $page[self::DESCRIPTION]);
            if (!$math && strpos($page[self::DESCRIPTION], "`") !== false) {
                $math = true;
                if (!isset($data["INCLUDE_SCRIPTS"])) {
                    $data["INCLUDE_SCRIPTS"] = [];
                }
                $data["INCLUDE_SCRIPTS"][] = "math";
            }
            unset($page['DESCRIPTION']);
            $page['OLD_DESCRIPTION'] = $description;
            $page[self::SOURCE_NAME] = $page['GROUP_NAME'];
            unset($page['GROUP_NAME']);
            if ($item['OWNER_ID'] == $user_id || $user_id == C\ROOT_ID) {
                $page['MEMBER_ACCESS'] = C\GROUP_READ_WIKI;
            }
            if ($updatable &&
                !$recent_found && !$math && $time - $item["PUBDATE"] <
                5 * C\ONE_MINUTE) {
                $recent_found = true;
                $data['SCRIPT'] .= 'doUpdate();';
            }
            $pages[$item["PUBDATE"] . sprintf("%04d", $j)] = $page;
            $j++;
        }
        if ($pages) {
            $sort($pages);
            $pages = array_slice($pages, $limit, $results_per_page);
        }
        return [$item_count, $pages];
    }
    /**
     * Used to add to $data information about the most recently view threads
     * and groups of the current user. This will be used to populate the
     * navigation dropdown in WikiView or WikiElement
     *
     * @param array &$data associative array of values to be echoed by the view
     * @param int $user_id id of user requesting thread info
     */
    public function calculateRecentFeedsAndThread(&$data, $user_id)
    {
        if ($user_id == C\PUBLIC_USER_ID) {
            return;
        }
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $personal_group_id = $group_model->getPersonalGroupId($user_id);
        $thread_ids = $parent->model("impression")->recent($user_id,
            C\THREAD_IMPRESSION, 5);
        $group_ids = $parent->model("impression")->recent($user_id,
            C\GROUP_IMPRESSION, 10);
        if (!empty($thread_ids)) {
            $data['RECENT_THREADS'] = [];
            foreach ($thread_ids as $recent_thread_id) {
                $thread_start_item = $group_model->getGroupItem(
                    $recent_thread_id);
                $thread_name = $thread_start_item['TITLE'] ?? "";
                if (!empty($thread_name) &&
                    (empty($data['JUST_THREAD']) ||
                    $recent_thread_id != $data['JUST_THREAD'])) {
                    $data['RECENT_THREADS'][$thread_name] =
                        htmlentities(B\feedsUrl("thread", $recent_thread_id,
                        true,  $data['CONTROLLER']));
                }
            }
        }
        if (!empty($group_ids)) {
            $data['RECENT_GROUPS'] = [];
            $thread_group_id = (empty($data["JUST_GROUP_ID"])) ?
                (empty($data["GROUP_ID"]) ? -1 : $data["GROUP_ID"] ) :
                $data["JUST_GROUP_ID"];
            $len = strlen(C\PERSONAL_GROUP_PREFIX);
            foreach ($group_ids as $recent_group_id) {
                $group_name = $group_model->getGroupName(
                    $recent_group_id);
                if (!empty($group_name) && !empty($thread_group_id) &&
                    $recent_group_id != $thread_group_id &&
                    $recent_group_id != $personal_group_id &&
                    substr($group_name, 0, $len) !=
                        C\PERSONAL_GROUP_PREFIX) {
                    $data['RECENT_GROUPS'][$group_name] =
                        htmlentities(B\feedsUrl("group",  $recent_group_id,
                        false,  $data['CONTROLLER']));
                }
            }
            if (count($data['RECENT_GROUPS']) > 5) {
                array_pop($data['RECENT_GROUPS']);
            }
        }
    }
    /**
     * Used to handle file uploads either to message posts or wiki pages
     *
     * @param string $group_id the group the message or wiki page is associated
     *      with
     * @param string $store_id the id of the message post or wiki page
     * @param string $sub_path used to specify sub-folder of default resource
     *      folder to copy to
     */
    public function handleResourceUploads($group_id, $store_id, $sub_path = "")
    {
        if (!isset($_FILES) || !is_array($_FILES)) {
            return self::UPLOAD_NO_FILES;
        }
        $keys = array_keys($_FILES);
        if (!isset($keys[0])) {
            return self::UPLOAD_NO_FILES;
        }
        $upload_field = $keys[0];
        $parent = $this->parent;
        $group_model = $parent->model("group");
        if (!isset($_FILES[$upload_field]['name'])) {
            return self::UPLOAD_NO_FILES;
        }
        $upload_parts = ['name', 'type', 'tmp_name', 'data'];
        $is_file_array = false;
        $num_files = 1;
        if (is_array($_FILES[$upload_field]['name'])) {
            $num_files =
                count($_FILES[$upload_field]['name']);
            $is_file_array = true;
        }
        $files = [];
        $upload_okay = true;
        for ($i = 0; $i < $num_files; $i ++) {
            foreach ($upload_parts as $part) {
                $file_part = ($is_file_array && isset(
                    $_FILES[$upload_field][$part][$i])) ?
                    $_FILES[$upload_field][$part][$i] :
                    ((!$is_file_array && isset(
                    $_FILES[$upload_field][$part])) ?
                    $_FILES[$upload_field][$part] :
                    false );
                if ($part == 'data') {
                    $files[$i][$part] = (empty($file_part) ) ? "" :
                        $file_part;
                    continue;
                }
                if ($file_part) {
                    $files[$i][$part] = $parent->clean(
                        $file_part, 'string');
                } else {
                    $upload_okay = false;
                    break 2;
                }
            }
        }
        if ($upload_okay) {
            foreach ($files as $file) {
                $group_model->copyFileToGroupPageResource(
                    $file['tmp_name'], $file['name'], $file['type'],
                    $group_id, $store_id, $sub_path, $file['data']);
            }
        }
        if (!$upload_okay) {
            return self::UPLOAD_FAILED;
        }
        return self::UPLOAD_SUCCESS;
    }
    /**
     * Used to set up GroupfeedView to draw a users group feeds grouped
     * by group names as opposed to as a linear list of thread and post
     * titles
     *
     * @param int $user_id id of current user
     * @param int $limit lower bound on the groups to display feed data for
     * @param int $results_per_page number of groups to display feed data
     *      for
     * @param string $controller_name name of controller on which this
     *      this component lives (either admin or group). Used by
     *      view to draw expand or collapse link
     * @param array $data field data for view to draw itself
     */
    public function calculateGroupedFeeds($user_id, $limit, $results_per_page,
        $controller_name, $data)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $data['MODE'] = 'grouped';
        $data['group_sorts'] = [ "name_asc" =>
            html_entity_decode(tl('social_component_name_asc')),
            "name_desc" => html_entity_decode(tl('social_component_name_desc')),
            "join_asc" => html_entity_decode(tl('social_component_join_asc')),
            "join_desc" => html_entity_decode(tl('social_component_join_desc')),
        ];
        $data['GROUP_SORT'] = (!empty($_REQUEST['group_sort']) &&
            isset($data['group_sorts'][$_REQUEST['group_sort']])) ?
            $_REQUEST['group_sort'] : "join_desc";
        $data["GROUP_FILTER"] = (empty($_REQUEST['group_filter'])) ?
            "" : $parent->clean($_REQUEST['group_filter'], "string");
        $search_array = [];
        if ($data["GROUP_FILTER"]) {
            if ($data["GROUP_FILTER"][0] == '=') {
                $name_clause = ["name", "=", substr($data["GROUP_FILTER"],1)];
            } else {
                $name_clause = ["name", "CONTAINS", $data["GROUP_FILTER"]];
            }
        } else {
            $name_clause = ["name", "", ""];
        }
        $name_clause[3] = ($data['GROUP_SORT'] == "name_asc") ?
            "ASC" : ($data['GROUP_SORT'] == "name_desc" ? "DESC" : "");
        if ($name_clause != ["name", "", "", ""]) {
            $search_array[] = $name_clause;
        }
        $join_clause = ["join_date", "", ""];
        $join_clause[3] = ($data['GROUP_SORT'] == "join_asc") ?
            "ASC" : ($data['GROUP_SORT'] == "join_desc" ? "DESC" : "");
        if ($join_clause != ["join_date", "", "", ""]) {
            $search_array[] = $join_clause;
        }
        $data['GROUPS'] = $group_model->getRows($limit, $results_per_page,
            $data['NUM_GROUPS'], $search_array, [$user_id, false]);
        $this->addActivityInfoToGroups($data);
        $data['LIMIT'] = $limit;
        $data['RESULTS_PER_PAGE'] = $results_per_page;
        $data['PAGING_QUERY'] = B\feedsUrl("", "",
            true, $controller_name);
        return $data;
    }
    /**
     *
     * @param array &$data
     */
    public function addActivityInfoToGroups(&$data)
    {
        $group_model = $this->parent->model("group");
        $impression_model = $this->parent->model("impression");
        $num_shown = count($data['GROUPS']);
        $user_id = $_SESSION['USER_ID'] ?? C\PUBLIC_USER_ID;
        for ($i = 0; $i < $num_shown; $i++) {
            $group = $data['GROUPS'][$i];
            $group_id = $group['GROUP_ID'];
            $item = $group_model->getMostRecentGroupPost($group_id);
            $most_recent_views = $impression_model->mostRecentGroupViews(
                $user_id, [$group_id]);
            $group["MOST_RECENT_VIEW"] =
                $most_recent_views[$group_id] ?? 0;
            $group['NEW_POSTS'] =
                $group_model->getGroupPostCount($group_id,
                $group["MOST_RECENT_VIEW"]);
            $group['NUM_POSTS'] = $group_model->getGroupPostCount($group_id);
            $group['NUM_THREADS'] =
                $group_model->getGroupThreadCount($group_id);
            $group['NUM_PAGES'] = $group_model->getGroupPageCount(
                $group['GROUP_ID']);
            $group["MEMBER_STATUS"] = $group_model->checkUserGroup(
                $user_id, $group_id);
            if (isset($item['TITLE'])) {
                $group["ITEM_TITLE"] = $item['TITLE'];
                $group["THREAD_ID"] = $item['PARENT_ID'];
            } else {
                $group["ITEM_TITLE"] = tl('social_component_no_posts_yet');
                $group["THREAD_ID"] = -1;
            }
            $data['GROUPS'][$i] = $group;
        }
        $data['NUM_SHOWN'] = $num_shown;
    }
    /**
     * Handles requests to reading, editing, viewing history, reverting, etc
     * wiki pages
     * @return array $data an associative array of form variables used to draw
     *     the appropriate wiki page
     */
    public function wiki()
    {
        $parent = $this->parent;
        $controller_name =
            (get_class($parent) == C\NS_CONTROLLERS . "AdminController") ?
                "admin" : "group";
        $base_url = C\SHORT_BASE_URL;
        list($data, $sub_path, $additional_substitutions, $clean_array,
            $strings_array, $page_defaults) = $this->initCommonWikiArrays(
                $controller_name, $base_url);
        $group_model = $parent->model("group");
        if (isset($_SESSION['USER_ID'])) {
            $user_id = $_SESSION['USER_ID'];
            $data['ADMIN'] = 1;
        } else {
            $user_id = C\PUBLIC_USER_ID;
        }
        $last_care_missing = 2;
        $missing_fields = false;
        $i = 0;
        if ($user_id == C\PUBLIC_USER_ID) {
            $_SESSION['LAST_ACTIVITY']['a'] = 'wiki';
            $_SESSION['LAST_ACTIVITY']['c'] = $controller_name;
        } else {
            unset($_SESSION['LAST_ACTIVITY']);
        }
        $missings = [];
        foreach ($clean_array as $field => $type) {
            if (isset($_REQUEST[$field])) {
                if ($field == 'page' && is_array($_REQUEST[$field])) {
                    $tmp = [];
                    foreach ($_REQUEST[$field]  as $key => $value) {
                        $key = $parent->clean($key, "string");
                        $value =  $parent->clean($value, "string");
                        $tmp[substr($key, 0, C\TITLE_LEN)] =
                            substr($value, 0, C\MAX_GROUP_PAGE_LEN);
                    }
                } else {
                    $tmp = $parent->clean($_REQUEST[$field], $type);
                }
                if (isset($strings_array[$field]) &&
                    !is_array($tmp)) {
                    $tmp = substr($tmp, 0, $strings_array[$field]);
                }
                if ($field == "page_name") {
                    $tmp = str_replace(" ", "_", $tmp);
                }
                if ($field == "group_name") {
                    $pre_id = $group_model->getGroupId($tmp);
                    if ($pre_id > 0) {
                        $group_id = $pre_id;
                        unset($missings[$field]);
                        if (empty($missings)) {
                            $missing_fields = false;
                        }
                    }
                }
                $$field = $tmp;
                if ($user_id == C\PUBLIC_USER_ID) {
                    $_SESSION['LAST_ACTIVITY'][$field] = $tmp;
                }
            } else if ($i < $last_care_missing) {
                $$field = false;
                $missing_fields = true;
                $missings[$field] = true;
            }
            $i++;
        }
        $data['RESOURCE_FILTER']  = (isset($resource_filter)) ?
            $resource_filter : "";
        $data['OPEN_IN_TABS'] = empty($_SESSION['OPEN_IN_TABS']) ? false :
            true;
        $data["SHARE_WALL_EDIT"] = false;
        if (!empty($group_id)) {
            if (isset($share_wall_data) && !empty($page_name)) {
                $page_info = $group_model->getPageInfoByName($group_id,
                    $page_name, $data['CURRENT_LOCALE_TAG'], "edit");
                if (!empty($page_info['PAGE']) &&
                    $group_model->getPageType($page_info['PAGE']) == 'share') {
                    $page = $share_wall_data;
                    $page_id = $page_info['ID'];
                    $data["CAN_EDIT"] = true;
                    $data["SHARE_WALL_EDIT"] = true;
                }
            }
        } else if (!empty($page_id)) {
            $page_info = $group_model->getPageInfoByPageId($page_id);
            if (isset($page_info["GROUP_ID"])) {
                $group_id = $page_info["GROUP_ID"];
                unset($page_info);
            } else {
                $group_id = C\PUBLIC_GROUP_ID;
            }
        } else {
            $group_id = C\PUBLIC_GROUP_ID;
        }
        $group = $group_model->getGroupById($group_id, $user_id, true);
        if (!$group || !isset($group["OWNER_ID"])) {
            if ($data['MODE'] !== 'api') {
                if ($user_id == C\PUBLIC_USER_ID) {
                    $_REQUEST = ['c' => "admin", 'a' => '', C\CSRF_TOKEN => ''];
                    return $parent->redirectWithMessage(
                        tl("social_component_login_first"));
                }
                unset($_REQUEST["route"]);
                $_REQUEST['group_id'] = C\PUBLIC_GROUP_ID;
                return $parent->redirectWithMessage(
                    tl("social_component_no_group_access"), false, false,
                    true);
            } else {
                $data['errors'] =  [];
                $data['errors'][] = tl("social_component_no_group_access");
            }
            $group_id = C\PUBLIC_GROUP_ID;
            $group = $group_model->getGroupById($group_id, $user_id);
        } else {
            if ($group["OWNER_ID"] == $user_id ||
                ($group["STATUS"] == C\ACTIVE_STATUS &&
                $group["MEMBER_ACCESS"] == C\GROUP_READ_WIKI)
                && $user_id != C\PUBLIC_USER_ID) {
                $data["CAN_EDIT"] = true;
            }
        }
        if ($group_id == C\PUBLIC_GROUP_ID) {
            $read_address = "[{controller_and_page}]";
        } else {
            $read_address = htmlentities(B\wikiUrl("", true, '[{controller}]',
                $group_id)) . "[{token}]&amp;page_name=";
        }
        if (isset($_REQUEST["arg"])) {
            switch ($_REQUEST["arg"]) {
                case "edit":
                    $page_id = isset($page_id) ? $page_id : null;
                    $page_name = isset($page_name) ? $page_name : null;
                    $page = isset($page) ? $page : null;
                    $edit_reason = isset($edit_reason) ? $edit_reason: null;
                    $this->editWiki($data, $user_id, $group_id, $group,
                        $page_id, $page_name, $page, $page_defaults, $sub_path,
                        $edit_reason, $missing_fields, $read_address,
                        $additional_substitutions);
                    break;
                case "history":
                    if (!isset($page_id) || !$page_id) {
                        break;
                    }
                    $data["MODE"] = "history";
                    $data["PAGE_NAME"] = "history";
                    $limit = isset($limit) ? $limit : 0;
                    $num = (isset($_SESSION["MAX_PAGES_TO_SHOW"]) &&
                        $_SESSION["MAX_PAGES_TO_SHOW"] > 0) ?
                       $_SESSION["MAX_PAGES_TO_SHOW"] :
                       C\DEFAULT_ADMIN_PAGING_NUM;
                    $default_history = true;
                    if (isset($show)) {
                        $page_info = $group_model->getHistoryPage(
                            $page_id, $show);
                        if ($page_info) {
                            $data["MODE"] = "show";
                            $default_history = false;
                            $data["PAGE_NAME"] = $page_info["PAGE_NAME"];
                            $parser = new WikiParser($read_address,
                                $additional_substitutions);
                            $parsed_page = $parser->parse($page_info["PAGE"]);
                            $data["PAGE_ID"] = $page_id;
                            $data[C\CSRF_TOKEN] =
                                $parent->generateCSRFToken($user_id);
                            $history_link = "?c={$data['CONTROLLER']}&amp;".
                                "a=wiki&amp;". C\CSRF_TOKEN.'='.
                                $data[C\CSRF_TOKEN].
                                '&amp;arg=history&amp;page_id='.
                                $data['PAGE_ID'];
                            $data["PAGE"] =
                                "<div>&nbsp;</div>".
                                "<div class='black-box back-dark-gray'>".
                                "<div class='float-opposite'>".
                                "<a href='$history_link'>".
                                tl("social_component_back") . "</a></div>".
                                tl("social_component_history_page",
                                $data["PAGE_NAME"], date("c", $show)) .
                                "</div>" . $parsed_page;
                            $data["DISCUSS_THREAD"] =
                                $page_info["DISCUSS_THREAD"];
                        }
                    } else if (!empty($diff) &&
                        isset($diff1) && isset($diff2)) {
                        $page_info1 = $group_model->getHistoryPage(
                            $page_id, $diff1);
                        $page_info2 = $group_model->getHistoryPage(
                            $page_id, $diff2);
                        $data["MODE"] = "diff";
                        $default_history = false;
                        $data["PAGE_NAME"] = $page_info2["PAGE_NAME"];
                        $data["PAGE_ID"] = $page_id;
                        $data[C\CSRF_TOKEN] =
                            $parent->generateCSRFToken($user_id);
                        $history_link = htmlentities(B\controllerUrl(
                            $data['CONTROLLER'],true)) .
                            "a=wiki&amp;".C\CSRF_TOKEN.'='.
                            $data[C\CSRF_TOKEN].
                            '&amp;arg=history&amp;page_id='.
                            $data['PAGE_ID'];
                        $out_diff = "<div>+++ {$data["PAGE_NAME"]}\t".
                            "''$diff1''\n";
                        $out_diff .= "<div>--- {$data["PAGE_NAME"]}\t".
                            "''$diff2''\n";
                        $out_diff .= L\diff($page_info2["PAGE"],
                            $page_info1["PAGE"], true);
                        $data["PAGE"] =
                            "<div>&nbsp;</div>".
                            "<div class='black-box back-dark-gray'>".
                            "<div class='float-opposite'>".
                            "<a href='$history_link'>".
                            tl("social_component_back") . "</a></div>".
                            tl("social_component_diff_page",
                            $data["PAGE_NAME"], date("c", $diff1),
                            date("c", $diff2)) .
                            "</div>" . "$out_diff";
                    } else if (isset($revert) && $data["CAN_EDIT"]) {
                        $page_info = $group_model->getHistoryPage(
                            $page_id, $revert);
                        if ($page_info) {
                            $action = "wikiupdate_".
                                "group=".$group_id."&page=" .
                                $page_info["PAGE_NAME"];
                            if (!$parent->checkCSRFTime(C\CSRF_TOKEN,
                                $action)) {
                                $data['SCRIPT'] .=
                                    "doMessage('<h1 class=\"red\" >".
                                    tl('social_component_wiki_edited_elsewhere')
                                    . "</h1>');";
                                break;
                            }
                            $group_model->revertResources($page_id, $group_id,
                                $revert);
                            $group_model->setPageName($user_id,
                                $group_id, $page_info["PAGE_NAME"],
                                $page_info["PAGE"],
                                $data['CURRENT_LOCALE_TAG'],
                                tl('social_component_page_revert_to',
                                date('c', $revert)), "", "", $read_address,
                                $additional_substitutions);
                            return $parent->redirectWithMessage(
                                tl("social_component_page_reverted"),
                                ['arg', 'page_name', 'page_id']);
                        } else {
                            return $parent->redirectWithMessage(
                                tl("social_component_revert_error"),
                                ['arg', 'page_name', 'page_id']);
                        }
                    }
                    if (empty($data["DISCUSS_THREAD"])) {
                        $page_info = $group_model->getPageInfoByPageId(
                            $page_id);
                        $data["DISCUSS_THREAD"] =
                            empty($page_info["DISCUSS_THREAD"]) ? -1 :
                            $page_info["DISCUSS_THREAD"];
                    }
                    if ($default_history) {
                        $data["LIMIT"] = $limit;
                        $data["RESULTS_PER_PAGE"] = $num;
                        list($data["TOTAL_ROWS"], $data["PAGE_NAME"],
                            $data["HISTORY"]) =
                            $group_model->getPageHistoryList($page_id, $limit,
                            $num);
                        if ((!isset($diff1) || !isset($diff2))) {
                            $data['diff1'] = $data["HISTORY"][0]["PUBDATE"]
                                ?? 0;
                            $data['diff2'] = $data["HISTORY"][0]["PUBDATE"]
                                ?? 0;
                            if (count($data["HISTORY"]) > 1) {
                                $data['diff2'] = $data["HISTORY"][1]["PUBDATE"];
                            }
                        }
                    }
                    $data['PAGE_ID'] = $page_id;
                    break;
                case "media":
                    $this->mediaWiki($data, $group_id, $page_id, $sub_path);
                    break;
                case "media-detail-edit":
                case "media-detail-read":
                    $this->mediaWikiDetail($data, $group_id, $page_id,
                        $sub_path);
                    break;
                case "pages":
                    $data["MODE"] = "pages";
                    $limit = isset($limit) ? $limit : 0;
                    $num = (isset($_SESSION["MAX_PAGES_TO_SHOW"]) &&
                        $_SESSION["MAX_PAGES_TO_SHOW"] > 0) ?
                       $_SESSION["MAX_PAGES_TO_SHOW"] :
                       C\DEFAULT_ADMIN_PAGING_NUM;
                    array_pop($data['sort_fields']);
                    array_pop($data['sort_fields']);
                    if (!empty($_REQUEST['sort']) && in_array($_REQUEST['sort'],
                        array_keys($data['sort_fields']))) {
                        if (!empty($_SESSION['media_sorts']) &&
                            count($_SESSION['media_sorts']) > 10) {
                            $first_key = array_key_first(
                                $_SESSION['media_sorts']);
                            unset($_SESSION['media_sorts'][$first_key]);
                        }
                        $_SESSION['media_sorts']['pages'] = $_REQUEST['sort'];
                    }
                    $data['CURRENT_SORT'] = $_SESSION['media_sorts']["pages"]
                        ?? "";
                    $filter = (empty($filter)) ? "" : $filter;
                    if (isset($page_name)) {
                        $data['PAGE_NAME'] = $page_name;
                    }
                    $data["LIMIT"] = $limit;
                    $data["RESULTS_PER_PAGE"] = $num;
                    $data["FILTER"] = preg_replace("/\s+/u", " ", $filter);
                    $filter = preg_replace("/\s+/u", "_", $filter);
                    $search_page_info = false;
                    if ($filter != "") {
                        $search_page_info = $group_model->getPageInfoByName(
                            $group_id, $filter, $data['CURRENT_LOCALE_TAG'],
                            "read");
                    }
                    if (!$search_page_info) {
                        list($data["TOTAL_ROWS"], $data["PAGES"]) =
                            $group_model->getPageList(
                            $group_id, $data['CURRENT_LOCALE_TAG'], $filter,
                            $data['CURRENT_SORT'], $limit, $num);
                    } else {
                        $data["MODE"] = "read";
                        $page_name = $data["FILTER"];
                    }
                    break;
                case 'relationships':
                    $data["MODE"] = "relationships";
                    $data["PAGE_NAME"] = "related";
                    if (empty($page_id)) {
                        break;
                    }
                    $page_info = $group_model->getPageInfoByPageId(
                        $page_id);
                    if (!isset($page_name)) {
                        $page_name = empty($page_info['PAGE_NAME']) ? "links" :
                            $page_info['PAGE_NAME'];
                    }
                    $limit = isset($limit) ? $limit : 0;
                    $num = (isset($_SESSION["MAX_PAGES_TO_SHOW"]) &&
                        $_SESSION["MAX_PAGES_TO_SHOW"] > 0) ?
                        $_SESSION["MAX_PAGES_TO_SHOW"] :
                        C\DEFAULT_ADMIN_PAGING_NUM;
                    $data["PAGE_ID"] = $page_id;
                    $data["PAGE_NAME"] = $page_name;
                    $data["DISCUSS_THREAD"] = empty($page_info["DISCUSS_THREAD"]
                        ) ? -1 : $page_info['DISCUSS_THREAD'];
                    $data["GROUP_ID"] = $page_info["GROUP_ID"];
                    $data["LIMIT"] = $limit;
                    $data["RESULTS_PER_PAGE"] = $num;
                    list($data["TOTAL_ROWS"], $data["RELATIONSHIPS"]) =
                        $group_model->getRelationshipsToFromPage($page_id,
                        $limit, $num);
                    //only one relationship so select
                    if (count($data["RELATIONSHIPS"]) == 1) {
                        $current = current($data["RELATIONSHIPS"]);
                        $_REQUEST["reltype"] =
                            $current["RELATIONSHIP_TYPE"];
                    }
                    if (isset($_REQUEST["reltype"])) {
                        $rel_type = $parent->clean($_REQUEST["reltype"],
                            "string");
                        $data["REL-TYPE"] = $rel_type;
                        $data["GROUP_ID"] = $group_id;
                        //clean up
                        if (!empty($page_id)) {
                            $page_info = $group_model->getPageInfoByPageId(
                                $page_id);
                            if (!isset($page_name)) {
                                $page_name = empty($page_info['PAGE_NAME'])
                                    ? "rel-types" : $page_info['PAGE_NAME'];
                            }
                            $limit = isset($limit) ? $limit : 0;
                            $num = (isset($_SESSION["MAX_PAGES_TO_SHOW"]) &&
                                $_SESSION["MAX_PAGES_TO_SHOW"] > 0) ?
                                $_SESSION["MAX_PAGES_TO_SHOW"] :
                                C\DEFAULT_ADMIN_PAGING_NUM;
                            $data["PAGE_ID"] = $page_id;
                            $data["PAGE_NAME"] = $page_name;
                            $data["DISCUSS_THREAD"] =
                                empty($page_info["DISCUSS_THREAD"] ) ? -1 :
                                $page_info['DISCUSS_THREAD'];
                            $data["GROUP_ID"] = $page_info["GROUP_ID"];
                            $data["LIMIT"] = $limit;
                            $data["RESULTS_PER_PAGE"] = $num;
                            list($data["TOTAL_TO_PAGES"],
                                $data["PAGES_THAT_LINK_TO"],
                                $data["TOTAL_FROM_PAGES"],
                                $data["PAGES_THAT_LINK_FROM"]) =
                                $group_model->pagesLinkedWithRelationship(
                                    $page_id, $data["GROUP_ID"],
                                    $data["PAGE_NAME"], $rel_type, $limit,$num);
                        }
                    }
                    break;
                case 'source':
                    if (isset($_REQUEST['caret']) &&
                       isset($_REQUEST['scroll_top'])
                            && !isset($page)) {
                        $caret = $parent->clean($_REQUEST['caret'],
                            'int');
                        $scroll_top = $parent->clean($_REQUEST['scroll_top'],
                            'int');
                        $data['SCRIPT'] .= "wiki = elt('wiki-page');".
                            "if (wiki.setSelectionRange) { " .
                            "   wiki.focus();" .
                            "   wiki.setSelectionRange($caret, $caret);".
                            "} ".
                            "wiki.scrollTop = $scroll_top;";
                    }
                    $data["MODE"] = "source";
                    $data["settings"] = (!empty($_REQUEST['settings']));
                    $data["resources"] = (!empty($_REQUEST['resources']));
                    $page_info = $group_model->getPageInfoByName($group_id,
                        $page_name, $data['CURRENT_LOCALE_TAG'], 'resources');
                    /* if page not yet created than $page_info will be null
                       so in the below $page_info['ID'] won't be set.
                     */
                    if (isset($page_info['ID'])) {
                        $data['RESOURCES_INFO'] =
                            $group_model->getGroupPageResourceUrls($group_id,
                            $page_info['ID'], $sub_path);
                    } else {
                        $data['RESOURCES_INFO'] = [];
                    }
                    break;
            }
        }
        if (!$page_name) {
            $page_name = tl('social_component_main');
        }
        $data["GROUP"] = $group;
        if (in_array($data["MODE"], ["api", "read", "edit", "media",
            "source"])) {
            // history action might set page, otherwise...
            if (empty($data["PAGE"]) && empty($data['RESOURCE_NAME'])) {
                $data["PAGE_NAME"] = $page_name;
                if (!empty($search_page_info)) {
                    $page_info = $search_page_info;
                } else {
                    $page_info = $group_model->getPageInfoByName($group_id,
                        $page_name, $data['CURRENT_LOCALE_TAG'], $data["MODE"]);
                }
                $data["PAGE"] = $page_info["PAGE"] ?? "";
                $data["PAGE_ID"] = $page_info["ID"] ?? "";
                $data["DISCUSS_THREAD"] = $page_info["DISCUSS_THREAD"] ?? "";
            }
            if (empty($data["PAGE"]) &&
                $data['CURRENT_LOCALE_TAG'] != C\DEFAULT_LOCALE) {
                //fallback to default locale for translation
                $page_info = $group_model->getPageInfoByName(
                    $group_id, $page_name, C\DEFAULT_LOCALE, $data["MODE"]);
                $data["PAGE"] = $page_info["PAGE"] ?? "";
                $data["PAGE_ID"] = $page_info["ID"] ?? "" ;
                $data["DISCUSS_THREAD"] = $page_info["DISCUSS_THREAD"] ?? "";
            }
            $view = $parent->view($data['VIEW']);
            $parent->parsePageHeadVarsView($view, $data["PAGE_ID"],
                $data["PAGE"]);
            if ($data['MODE'] == "read" || empty($_REQUEST['n'])) {
                $data["PAGE"] = $view->page_objects[$data["PAGE_ID"]];
            }
            $data["HEAD"] = $view->head_objects[$data["PAGE_ID"]];
            if (isset($data["HEAD"]['page_type']) &&
                $data["HEAD"]['page_type'] == 'page_alias' &&
                $data["HEAD"]['page_alias'] != '' &&
                in_array($data['MODE'], ["read", 'api']) &&
                !isset($_REQUEST['noredirect']) ) {
                if ($data['MODE'] == 'api') {
                    $controller_name = "api";
                }
                return $parent->redirectLocation(B\wikiUrl(
                    $data["HEAD"]['page_alias'],
                    true, $controller_name, $group_id) . C\CSRF_TOKEN . '=' .
                    $parent->generateCSRFToken($user_id));
            } else if (isset($data["HEAD"]['page_type']) &&
                    $data["HEAD"]['page_type'] == 'url_shortener' &&
                    in_array($data['MODE'], ["read"])) {
                    $parent->redirectLocation($data["HEAD"]['url_shortener']);
            }
            if ($data['MODE'] == "read") {
                $data['GROUP_STATUS'] = $group['STATUS'];
                $data['JUST_THREAD'] = true;
                if ((!empty($_POST['RCSVFORM']) || !empty($_POST['CSVFORM']))
                    && !empty($_POST[C\CSRF_TOKEN])) {
                    $this->processWikiFormData($data, $user_id, $group_id,
                        $sub_path);
                }
                $this->initializeReadMode($data, $user_id, $group_id,
                    $sub_path);
            } else if (in_array($data['MODE'], ['edit', 'source'])) {
                foreach ($page_defaults as $key => $default) {
                    $data[$key] = $default;
                    if (isset($data["HEAD"][$key])) {
                        $data[$key] = $data["HEAD"][$key];
                    }
                }
                $this->initUserResourcePreferences($data);
                $scroll_id = "scroll-container-" .
                    L\crawlHash($data['PAGE_ID'] . $sub_path);
                $data['SCROLL_CONTAINER_ID'] = $scroll_id;
                $data['SCRIPT'] .= "initScrollPositionPreserver('".
                    $data['SCROLL_CONTAINER_ID'] . "');";
                if ($data['CURRENT_LAYOUT'] == 'detail') {
                    $data['DETAIL_SCROLL_ID'] = "detail-$scroll_id";
                    $data['SCRIPT'] .= "initScrollPositionPreserver('".
                        $data['DETAIL_SCROLL_ID'] . "');";
                }
                if (!empty($data['RESOURCE_NAME'])) {
                    $name_parts = pathinfo($data['RESOURCE_NAME']);
                    if (!empty($name_parts['extension']) &&
                        empty($data['RAW'])) {
                        switch ($name_parts['extension']) {
                            case 'csv':
                                $user_config = "";
                                if (!empty($_SESSION['USER_NAME'])) {
                                    $user_config .= ',user_name:'.
                                        json_encode($_SESSION['USER_NAME']);
                                }
                                $data['INCLUDE_SCRIPTS'][] = 'spreadsheet';
                                $data['SCRIPT'] .=
                                    'spreadsheet = new Spreadsheet(' .
                                    '"spreadsheet",' .
                                    $data["PAGE"] . ', {mode:"write"'.
                                    "$user_config});".
                                    'spreadsheet.draw();';
                                $data['SPREADSHEET'] = true;
                                break;
                        }
                    }
                }
                foreach (['settings', 'resources'] as $field) {
                    $data[$field] = "false";
                    if (isset($_REQUEST[$field]) &&
                         $_REQUEST[$field] == 'true') {
                        $data[$field] = "true";
                    }
                }
                $data['current_page_type'] = $data["page_type"];
                if ($data['current_page_type'] == 'url_shortener'
                    && $data['MODE'] == 'edit') {
                    $this->makeImpressionChart($data, C\WIKI_IMPRESSION,
                        C\ONE_DAY, $data['PAGE_ID'], "day_chart",
                        "day-chart");
                    $this->makeImpressionChart($data, C\WIKI_IMPRESSION,
                        C\ONE_MONTH, $data['PAGE_ID'], "month_chart",
                        "month-chart");
                    $this->makeImpressionChart($data, C\WIKI_IMPRESSION,
                        C\ONE_YEAR, $data['PAGE_ID'], "year_chart",
                        "year-chart");
                }
                $templates = $group_model->getTemplateMap($group_id,
                    $data['CURRENT_LOCALE_TAG']);
                /*
                    if the page id is not that of a template, then
                    we add the list of templates to the available
                    page_type page can be set to and we check
                    if page uses a template
                 */
                if (empty($templates["t" . $data["PAGE_ID"]])) {
                    $data['page_types'] = array_merge($data['page_types'],
                        $templates);
                    if (empty($_REQUEST['n']) &&
                        !empty($templates[$data['current_page_type']])) {
                        $template_name = $templates[$data['current_page_type']];
                        $template_info = $group_model->
                            getPageInfoByName($group_id, $template_name,
                            $data['CURRENT_LOCALE_TAG'], "read");
                        list( ,$tmp_page) = $parent->parsePageHeadVars(
                            $template_info['PAGE'], true);
                        $tmp_page = preg_replace("/{{text\|(.+?)\|(.+?)}}/",
                            "<input type='text' class='narrow-field'" .
                            " name='page[$1]' placeholder='$2'" .
                            " value='{{field|$1}}' >", $tmp_page);
                        $tmp_page = preg_replace("/{{area\|(.+?)\|(.+?)}}/",
                            "<textarea class='short-text-area'" .
                            " name='page[$1]' placeholder='$2'>" .
                            "{{field|$1}}</textarea>", $tmp_page);
                        if (empty($data['PAGE'])) {
                            $data['PAGE'] = preg_replace(
                                "/{{field\|(.+?)}}/", "", $tmp_page);
                        } else {
                            set_error_handler(null);
                            $page_data = @unserialize(base64_decode(
                                $data['PAGE']));
                            set_error_handler(C\NS_CONFIGS .
                                "yioop_error_handler");
                            if (is_array($page_data)) {
                                foreach ($page_data as
                                    $page_key => $page_value) {
                                    $tmp_page = preg_replace(
                                        "/{{field\|" .
                                        preg_quote($page_key, "/") .
                                        "}}/", $page_value, $tmp_page);
                                }
                            }
                            $data['PAGE'] = preg_replace(
                                "/{{field\|(.+?)}}/", "", $tmp_page);
                        }
                    }
                }
                $this->initializeWikiPageToggle($data);
                if (empty($data['RESOURCE_NAME'])) {
                    $this->initializeWikiEditor($data);
                }
            }
        }
        if (!empty($data['PAGE_ID'])) {
            $data['PAGE_HAS_RELATIONSHIPS'] =
                $group_model->countPageRelationships($data['PAGE_ID']);
        }
        $this->updateGetWikiImpressionInfo($data, $user_id, $group_id);
        return $data;
    }
    /**
     * Used to process form data associated with a wiki page with a form on it.
     * Such a form's data is stored in a CSV file
     *
     * @param array &$data associative array of values to be echoed by the view
     * @param int $user_id id of user requesting a wiki page
     * @param int $group_id group in which wiki page belongs
     * @param string $sub_path any path within wiki page folder for resources
     */
    public function processWikiFormData($data, $user_id, $group_id,
        $sub_path)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        if (empty($data['PAGE_ID']) || empty($group_id)) {
            return;
        }
        $default_folders = $group_model->getGroupPageResourcesFolders($group_id,
            $data['PAGE_ID']);
        $csv_filepath = $default_folders[0] . '/' . C\WIKI_FORM_CSV_FILE;
        $preserve_fields =
            ['arg', 'page_name', 'settings', 'caret', 'scroll_top', 'sf'];
        if (!$parent->checkCSRFToken($_POST[C\CSRF_TOKEN], $user_id, true)) {
            return $parent->redirectWithMessage(
                tl('social_component_page_data_expired'), $preserve_fields);
        }
        $page = $data['PAGE'];
        $tmp_page = preg_replace("/\[{form\-hash(.+?)}\]/", "[{form-hash}]",
            $page);
        $csv_form_hash = L\crawlHash(C\AUTH_KEY . $_POST[C\CSRF_TOKEN] .
            L\crawlHash($tmp_page));
        if ($csv_form_hash != $_POST['CSV_FORM_HASH']) {
            return $parent->redirectWithMessage(
                tl('social_component_page_integrity_issue'), $preserve_fields);
        }
        $csv_headers = [];
        if (empty($_POST['CSVFORM']['user_captcha_text'])) {
            return $parent->redirectWithMessage(
                tl('social_component_form_needs_captcha'), $preserve_fields);
        }
        $num_fields = count($_POST['CSVFORM'] ?? []) +
            count($_POST['RCSVFORM'] ?? []);
        if ($num_fields > C\MAX_WIKI_FORM_FIELDS) {
            return $parent->redirectWithMessage(
                tl('social_component_too_many_fields_form'), $preserve_fields);
        }
        $csv_form_info = [$_POST['CSVFORM'] ?? [] , $_POST['RCSVFORM'] ??[]];
        foreach ($csv_form_info as $csv_form_fields) {
            foreach ($csv_form_fields as $form_field => $field_type) {
                $form_field = substr(
                    $parent->clean($form_field, 'string'), 0, C\NAME_LEN);
                if ($field_type == 'submit') {
                } else if (in_array($form_field, $csv_headers)) {
                    continue;
                } else {
                    $csv_headers[] = $form_field;
                }
            }
        }
        if (file_exists($csv_filepath)) {
            if (filesize($csv_filepath) > C\MAX_WIKI_FORM_CSV_SIZE) {
                return $parent->redirectWithMessage(
                    tl('social_component_csv_too_big'), $preserve_fields);
            }
            $fh = fopen($csv_filepath, "a+");
        } else {
            $fh = fopen($csv_filepath, "w+");
            fputcsv($fh, $csv_headers);
        }
        $out_row = [];
        foreach ($csv_headers as $csv_header) {
            $is_required = (isset($_POST['RCSVFORM'][$csv_header])) ? 1 : 0;
            if ($is_required && empty($_POST[$csv_header])) {
                return $parent->redirectWithMessage(
                    tl('social_component_fill_required_fields'), array_merge(
                    $preserve_fields, $csv_headers));
            }
            if ($csv_header == 'user_captcha_text' &&
                (empty($_SESSION['captcha_text']) ||
                $_POST[$csv_header] != $_SESSION['captcha_text'])) {
                $parent->model("visitor")->updateVisitor(
                    L\remoteAddress(), "captcha_time_out");
                return $parent->redirectWithMessage(
                    tl('social_component_captcha_failed'), array_merge(
                    $preserve_fields, $csv_headers));
            }
            $header_type = $_POST['CSVFORM'][$csv_header] ?? "textfield";
            $clean_field = $parent->clean($_POST[$csv_header] ?? "", "string");
            if ($header_type == "submit") {
                continue;
            } else if ($header_type == "checkbox") {
                $clean_field = empty($clean_field) ? false : true;
            } else {
                $max_lengths = [
                    "radio" => C\NAME_LEN,
                    "textfield" => C\LONG_NAME_LEN,
                    "textarea" => C\TITLE_LEN
                ];
                $clean_field = substr($clean_field, 0,
                    $max_lengths[$header_type]);
            }
            $out_row[] = $clean_field;
        }
        fputcsv($fh, $out_row);
        fclose($fh);
        return $parent->redirectWithMessage(
            tl('social_component_choices_recorded'), $preserve_fields);
    }
    /**
     * Sets up view variables for wiki pages when in read mode. If
     * a user send a command to indicate a media resource on a media list
     * is not viewed, then also update session accordingly
     *
     * @param array &$data associative array of values to be echoed by the view
     * @param int $user_id id of user requesting a wiki page
     * @param int $group_id group in which wiki page belongs
     * @param string $sub_path any path within wiki page folder for resources
     */
    private function initializeReadMode(&$data, $user_id, $group_id, $sub_path)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $data["NOT_MEMBER"] = !$group_model->checkUserGroup($user_id, $group_id,
            C\ACTIVE_STATUS);
        if (isset($data["HEAD"]['page_header']) &&
            $data["HEAD"]['page_type'] != 'presentation') {
            $page_header = $group_model->getPageInfoByName($group_id,
                $data["HEAD"]['page_header'],
                $data['CURRENT_LOCALE_TAG'], $data["MODE"]);
            if (isset($page_header['PAGE'])) {
                $header_parts =
                    explode("END_HEAD_VARS", $page_header['PAGE']);
            }
            $data["PAGE_HEADER"] = (isset($header_parts[1])) ?
                $header_parts[1] : ($page_header['PAGE'] ?? "");
        }
        if (($data["HEAD"]['page_type'] ?? "") == 'share' &&
            !empty($data['PAGE_ID'])) {
            $last_edit = $group_model->getPageHistoryList(
                $data['PAGE_ID'], 0, 1);
            if (!empty($last_edit)) {
                $data['LAST_EDITOR'] = $last_edit[2][0]["USER_NAME"];
                $data['LAST_EDIT_TIME'] = $last_edit[2][0]["PUBDATE"];
                $share_expires = $data["HEAD"]['share_expires'] ?? C\FOREVER;
                if ($share_expires != C\FOREVER) {
                    $expires_time = $data['LAST_EDIT_TIME'] + $share_expires;
                    if (time() > $expires_time) {
                        $data['PAGE'] = "";
                    }
                }
            }
        }
        if (isset($data["HEAD"]['page_footer']) &&
            $data["HEAD"]['page_type'] != 'presentation') {
            $page_footer = $group_model->getPageInfoByName($group_id,
                $data["HEAD"]['page_footer'], $data['CURRENT_LOCALE_TAG'],
                $data["MODE"]);
            if (isset($page_footer['PAGE'])) {
                $footer_parts =
                    explode("END_HEAD_VARS", $page_footer['PAGE']);
            }
            $data['PAGE_FOOTER'] = (isset($footer_parts[1])) ?
                $footer_parts[1] : ($page_footer['PAGE'] ?? "");
        }
        $data["INCLUDE_SCRIPTS"] ??= [];
        if (strpos($data["PAGE"], "`") !== false) {
            $data["INCLUDE_SCRIPTS"][] = "math";
        }
        if (strpos($data["PAGE"], "canvas-360") !== false) {
            $data["INCLUDE_SCRIPTS"] = array_merge($data["INCLUDE_SCRIPTS"],
                ["wglu-program", "vr-panorama", "vr-util"]);
            $data["SCRIPT"] .= ";var tl_elt = elt('tl'); tl_elt.enter_vr ='".
                tl('enter_vr') . "'; tl_elt.exit_vr = '".tl('exit_vr')."';";
        }
        if (preg_match("/\(\(resource(\-?[a-z]+)?\:(.+?)csv(.+?)\|(.+?)\)\)/ui",
            $data["PAGE"])) {
            $data["PAGE"] = $group_model->insertResourcesParsePage($group_id,
                 $data["PAGE_ID"], $data['CURRENT_LOCALE_TAG'], $data["PAGE"],
                 "", "admin", true);
        }
        if (stripos($data["PAGE"], "chart_data") !== false) {
            if (!in_array("chart", $data["INCLUDE_SCRIPTS"])) {
                $data["INCLUDE_SCRIPTS"][] = "chart";
            }
            if (strpos($data["SCRIPT"], "new Chart") === false) {
                if ($_SERVER["MOBILE"]) {
                    $properties = ["width" => 340, "height" => 300,
                        "tick_font_size" => 8];
                } else {
                    $properties = ["width" => 700, "height" => 500];
                }
                $data['SCRIPT'] .= <<< 'EOD'
                for (var chart_elt in chart_data) {
                    var chart = new Chart(
                        'chart_' + chart_elt,
                        chart_data[chart_elt],
                        chart_config[chart_elt]);
                    chart.draw();
                }
EOD;
            }
        }
        if (strpos($data["PAGE"], "[{image-captcha}]") !== false) {
            $parent->setupGraphicalCaptchaViewData($data);
        }
        if (strpos($data["PAGE"], "spreadsheet_data") !== false) {
            if (!in_array("spreadsheet", $data["INCLUDE_SCRIPTS"])) {
                $data["INCLUDE_SCRIPTS"][] = "spreadsheet";
            }
            if (strpos($data["SCRIPT"], "new Spreadsheet") === false) {
                $data['SCRIPT'] .= <<< 'EOD'
                for (var spreadsheet_elt in spreadsheet_data) {
                    var spreadsheet = new Spreadsheet(
                        'spreadsheet_' + spreadsheet_elt,
                        spreadsheet_data[spreadsheet_elt],
                        spreadsheet_config[spreadsheet_elt]);
                    spreadsheet.draw();
                }
EOD;
            }
            $data['SPREADSHEET'] = true;
        }
        if (empty($data["HEAD"]['page_type'])) {
            return;
        }
        //handles template page types for read case
        if ($data["HEAD"]['page_type'][0] == 't' &&
            is_numeric(substr($data["HEAD"]['page_type'], 1))) {
            $templates = $group_model->getTemplateMap($group_id,
                $data['CURRENT_LOCALE_TAG']);
            if (empty($_REQUEST['n']) &&
                !empty($templates[$data["HEAD"]['page_type']])) {
                $template_name = $templates[$data["HEAD"]['page_type']];
                $template_info = $group_model->
                    getPageInfoByName($group_id, $template_name,
                    $data['CURRENT_LOCALE_TAG'], "read");
                list( ,$tmp_page) = $parent->parsePageHeadVars(
                    $template_info['PAGE'], true);
                $tmp_page = preg_replace("/{{(area|text)\|(.+?)\|(.+?)}}/",
                    "{{field|$2}}", $tmp_page);
                if (empty($data['PAGE'])) {
                    $data['PAGE'] = preg_replace(
                        "/{{field\|(.+?)}}/", "", $tmp_page);
                } else {
                    set_error_handler(null);
                    $page_data = @unserialize(base64_decode(substr(
                        $data['PAGE'], strlen('<div>'),
                        -strlen('</div>'))));
                    set_error_handler(C\NS_CONFIGS . "yioop_error_handler");
                    if (is_array($page_data)) {
                        foreach ($page_data as
                            $page_key => $page_value) {
                            $tmp_page = preg_replace(
                                "/{{field\|" . preg_quote($page_key, "/") .
                                "}}/", $page_value, $tmp_page);
                        }
                    }
                    $data['PAGE'] = preg_replace(
                        "/{{field\|(.+?)}}/", "", $tmp_page);
                }
            }
        } else if ($data["HEAD"]['page_type'] == 'page_and_feedback') {
            $just_thread = $data['DISCUSS_THREAD'];
            $thread_parent =
                $group_model->getGroupItem($just_thread);
            $edit_or_source = ($data["CAN_EDIT"]) ? "edit" : "source";
            $search_array = [
                ["parent_id", "=", $just_thread, ""],
                ["pub_date", "", "", "DESC"]];
            $limit = (!empty($_REQUEST['limit'])) ?
                $parent->clean($_REQUEST['limit'], 'int') : 0;
            $results_per_page =  (!empty($_REQUEST['num'])) ?
                $parent->clean($_REQUEST['num'], 'int') :
                C\NUM_RESULTS_PER_PAGE;
            list($item_count, $pages) = $this->initializeFeedItems($data, [],
                $user_id, $search_array, -2, "krsort",
                $limit, $results_per_page);
            if ($limit + count($pages) == $item_count) {
                $begin_page = array_pop($pages);
                $data["WIKI_MEMBER_ACCESS"] = $begin_page["MEMBER_ACCESS"];
                $data['WIKI_PARENT_ID'] = $data['DISCUSS_THREAD'];
                $data['WIKI_GROUP_ID'] = $group_id;
            }
            $item_count--;
            $data['TOTAL_ROWS'] = $item_count;
            if ($data['TOTAL_ROWS'] == 0) {
                $data['NO_POSTS_YET'] = true;
            }
            $data['INCLUDE_SCRIPTS'][] =  "wiki";
            $data['LIMIT'] = $limit;
            $data['RESULTS_PER_PAGE'] = $results_per_page;
            $data['PAGES'] = $pages;
            $data[C\CSRF_TOKEN] = $parent->generateCSRFToken($user_id);
            $data['PAGING_QUERY'] = htmlentities(B\wikiUrl($data['PAGE_NAME'],
                true, $data['CONTROLLER'], $group_id)) .
                C\CSRF_TOKEN . '='. $data[C\CSRF_TOKEN] .
                "&amp;page_type=page_and_feedback";
            $data['WIKI_FEED_BASE'] = C\BASE_URL . "?c=". $data['CONTROLLER'] .
                "&amp;a=groupFeeds&amp;just_thread=".$data['DISCUSS_THREAD'] .
                "&amp;". C\CSRF_TOKEN . '='. $data[C\CSRF_TOKEN] .
                "&amp;page_type=page_and_feedback&amp;page_name=" .
                $data['PAGE_NAME'];
            if ($data['VIEW'] != 'api') {
                $data['SCRIPT'] .= " let nextPage = initNextResultsPage(" .
                    "$limit, {$data['TOTAL_ROWS']}, $results_per_page, ".
                    "'{$data['PAGING_QUERY']}', '', " .
                    "'results-container', 'result-batch');\n";
            }
        } else if ($data["HEAD"]['page_type'] == 'media_list') {
            $data['INCLUDE_SCRIPTS'][] =  "wiki";
            $data['RESOURCES_INFO'] =
                $group_model->getGroupPageResourceUrls($group_id,
                    $data['PAGE_ID'], $sub_path,
                    needs_descriptions_format:
                    $data["HEAD"]['update_description'] ?? "");
            $thumb_folder = $data['RESOURCES_INFO']['thumb_folder'] ?? "";
            if (!empty($thumb_folder)) {
                $fp = fopen(self::RECOMMENDATION_FILE, "a");
                fwrite($fp, $group_id . "###" . $data['PAGE_ID'] . "###" .
                    $thumb_folder . "\n");
                fclose($fp);
            }
            $this->initUserResourcePreferences($data);
            $scroll_id = "scroll-container-" .
                L\crawlHash($data['PAGE_ID'] . $sub_path);
            $data['SCROLL_CONTAINER_ID'] = $scroll_id;
            $data['SCRIPT'] .= "initScrollPositionPreserver('".
                $data['SCROLL_CONTAINER_ID'] . "');";
            if ($data['CURRENT_LAYOUT'] == 'detail') {
                $data['DETAIL_SCROLL_ID'] = "detail-$scroll_id";
                $data['SCRIPT'] .= "initScrollPositionPreserver('".
                    $data['DETAIL_SCROLL_ID'] . "');";
            }
        } else if ($data["HEAD"]['page_type'] == 'presentation' &&
            $data['CONTROLLER'] == 'group') {
            $data['page_type'] = 'presentation';
            $data['INCLUDE_SCRIPTS'][] =  "slidy";
            $data['INCLUDE_STYLES'][] =  "slidy";
        }
    }
    /**
     * Extracts from session information about resource sort, layout, etc,
     * so that WikiElement can draw resources correctly
     *
     * @param array &$data associative array of values to be echoed by the view
     */
    public function initUserResourcePreferences(&$data)
    {
        $parent = $this->parent;
        // Delete a marked diamond icon from video list
        $changed = false;
        if (!empty($_REQUEST['clear']) && !empty($_SESSION['seen_media'])
            && is_array($_SESSION['seen_media']) && !empty($data['PAGE_ID'])) {
            $media_name = $parent->clean($_REQUEST['clear'], 'file_name');
            $type = UrlParser::getDocumentType($media_name);
            if ($type != "") {
                $media_name = UrlParser::getDocumentFilename($media_name);
                $media_name = urlencode($media_name);
                $media_name = "$media_name.$type";
            }
            $sub_path = $data['SUB_PATH'] ?? "";
            $hash_id = L\crawlHash($data['PAGE_ID']. $media_name . $sub_path);
            if (in_array($hash_id, $_SESSION['seen_media'])) {
                $_SESSION['seen_media'] = array_diff($_SESSION['seen_media'],
                    [$hash_id]);
                $changed = true;
            }
        }
        $sub_path = $data['SUB_PATH'] ?? "";
        $folder_hash_id = L\crawlHash(($data['PAGE_ID'] ?? -1) . $sub_path);
        if (!empty($_REQUEST['sort']) && in_array($_REQUEST['sort'],
            array_keys($data['sort_fields']))) {
            if (!empty($_SESSION['media_sorts']) &&
                count($_SESSION['media_sorts']) > 10) {
                $first_key = array_key_first($_SESSION['media_sorts']);
                unset($_SESSION['media_sorts'][$first_key]);
            }
            $_SESSION['media_sorts'][$folder_hash_id] = $_REQUEST['sort'];
            $changed = true;
        }
        if (!empty($_REQUEST['layout']) && in_array($_REQUEST['layout'],
            ['list', 'grid', 'detail'])) {
            unset($_SESSION['layouts'][$folder_hash_id]);
            $_SESSION['layouts'][$folder_hash_id] = $_REQUEST['layout'];
            if (count($_SESSION['layouts']) > 10) {
                $first_key = array_key_first($_SESSION['layouts']);
                unset($_SESSION['layouts'][$first_key]);
            }
            $changed = true;
        }
        $data['CURRENT_LAYOUT'] = $_SESSION['layouts'][$folder_hash_id] ??
            "list";
        $data['CURRENT_SORT'] = $_SESSION['media_sorts'][$folder_hash_id] ?? "";
        if ($changed) {
            // only saves session in not PUBLIC_USER_ID
            $parent->model("user")->setUserSession($_SESSION['USER_ID'] ??
                C\PUBLIC_USER_ID, $_SESSION);
        }
        $this->sortWikiResources($data);
    }
    /**
     * Used to populate recent page and group activity dropdowns for a wiki
     * page and to update the recent page impressions so that this can be
     * calculated
     *
     * @param array &$data $data data to be sent to the view, will be modified
     *  according to impression info.
     * @param int $user_id id of the user requesting to change the given wiki
     *  page
     * @param int $group_id id of the group the wiki page belongs to
     */
    private function updateGetWikiImpressionInfo(&$data, $user_id, $group_id)
    {
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $personal_group_id = $group_model->getPersonalGroupId($user_id);
        if (!empty($data['PAGE_ID']) && $data['MODE'] != 'api') {
            $parent->model("impression")->add($user_id, $data['PAGE_ID'],
                C\WIKI_IMPRESSION);
            $parent->model("impression")->add($user_id, $group_id,
                C\GROUP_IMPRESSION);
        }
        if ($user_id != C\PUBLIC_USER_ID) {
            $page_ids = $parent->model("impression")->recent($user_id,
                C\WIKI_IMPRESSION, 6);
            if (!empty($page_ids)) {
                $data['RECENT_PAGES'] = [];
                $i = 0;
                foreach ($page_ids as $recent_page_id) {
                    $page_info = $group_model->getPageInfoByPageId(
                        $recent_page_id);
                    $group_name = empty($page_info['GROUP_ID']) ? "" :
                        $group_model->getGroupName($page_info['GROUP_ID']);
                    $len = strlen(C\PERSONAL_GROUP_PREFIX);
                    if (substr($group_name, 0, $len) ==
                        C\PERSONAL_GROUP_PREFIX) {
                        continue;
                    }
                    if (!empty($page_info) && (empty($data['PAGE_NAME']) ||
                        ($page_info['PAGE_NAME'] != $data['PAGE_NAME']))) {
                        $data['RECENT_PAGES'][
                            $page_info['PAGE_NAME']. "@". $group_name] =
                            htmlentities(B\wikiUrl($page_info['PAGE_NAME'],
                            true, $data['CONTROLLER'],
                            $page_info['GROUP_ID']));
                        if ($data['MODE'] == 'edit') {
                            $data['RECENT_PAGES'][$page_info['PAGE_NAME']
                                 . "@" . $group_name] .=
                                "&amp;arg=edit&amp;";
                        }
                    }
                    $i++;
                    if ($i > 5) {
                        break;
                    }
                }
            }
            $group_ids = $parent->model("impression")->recent($user_id,
                C\GROUP_IMPRESSION, 6);
            if (!empty($group_ids)) {
                $data['RECENT_GROUPS'] = [];
                foreach ($group_ids as $recent_group_id) {
                    if ($recent_group_id == $personal_group_id) {
                        continue;
                    }
                    $group_name = $group_model->getGroupName(
                        $recent_group_id);
                    if (!empty($group_name) &&
                        ($recent_group_id != $group_id ||
                        empty($data['PAGE_NAME']) ||
                        $data['PAGE_NAME'] != 'Main')) {
                        $data['RECENT_GROUPS'][$group_name] =
                            htmlentities(B\wikiUrl("Main" , true,
                            $data['CONTROLLER'], $recent_group_id));
                        if ($data['MODE'] == 'edit') {
                            $data['RECENT_GROUPS'][$group_name] .=
                                "&amp;arg=edit&amp;";
                        }
                    }
                }
            }
            if (!empty($data['RECENT_GROUPS']) &&
                count($data['RECENT_GROUPS']) > 5) {
                array_pop($data['RECENT_GROUPS']);
            }
        }
    }
    /**
     * Used to sort the resources on a wiki pages either for display in case
     * of reading a media list or to help find resources in the case of a
     * user using edit mode
     *
     * @param array &$data data to be sent to the view. The
     *  $data["RESOURCES_INFO"]['resources'] array of resources will be
     *  sorted according to the wiki page's settings as given in
     *  $data["HEAD"]['sort']
     */
        public function sortWikiResources(&$data)
    {
        if (empty($data["RESOURCES_INFO"]['resources'])) {
            return;
        }
        $folder_hash_id = L\crawlHash(($data['PAGE_ID'] ?? "").
            ($data['SUB_PATH'] ?? ""));
        if (!empty($_SESSION['media_sorts'][$folder_hash_id])) {
            list($sort_field, $direction) = explode("_",
                $_SESSION['media_sorts'][$folder_hash_id]);
            $callback = ($direction == 'asc') ?
                "orderCallback" : "rorderCallback";
            if ($sort_field == 'name') {
                $callback = ($direction == 'asc') ?
                    "stringROrderCallback" : "stringOrderCallback";
            }
        } else {
            if (empty($data["HEAD"]['default_sort'])) {
                return;
            }
            set_error_handler(null);
            $sort_map = @unserialize(L\webdecode(
                $data["HEAD"]['default_sort']));
            set_error_handler(C\NS_CONFIGS . "yioop_error_handler");
            $sort_key = (empty($data['SUB_PATH'])) ? "." : $data['SUB_PATH'];
            $sort_key = rtrim($sort_key, '/');
            if (empty($sort_map[$sort_key])) {
                return;
            }
            $sort_field = substr($sort_map[$sort_key], 1);
            $callback = ($sort_map[$sort_key][0] == 'r') ?
                "rorderCallback" : "orderCallback";
            if ($sort_field == 'name') {
                $callback = ($sort_map[$sort_key][0] == 'r') ?
                    "stringROrderCallback" : "stringOrderCallback";
            }
        }
        $callback_name = C\NS_LIB . $callback;
        $callback_name(null, null, $sort_field);
        usort($data["RESOURCES_INFO"]["resources"], C\NS_LIB . $callback);
    }
    /**
     * Used to handle edit settings and resources actions for the wiki()
     * activity
     *
     * This method was pulled out of the giant switch case in wiki() and the
     * refactoring still needs some work. Hence, the awkward parameter list
     * below.
     *
     * @param array &$data $data data to be sent to the view, will be modified
     *  according to the edit action.
     * @param int $user_id id of the user requesting to change the given wiki
     *  page
     * @param int $group_id id of the group the wiki page belongs to
     * @param array $group associative array of info about the group wiki
     *  page belongs to
     * @param int $page_id if of wiki page being edited
     * @param string $page_name string name of wiki page being edited
     * @param string $page cleaned wiki page that came from $_REQUEST, if any
     * @param array $page_defaults associative array system-wide defaults
     *  for page settings of any wiki page
     * @param string $sub_path sub resource folder being edited of wiki page, if
     *  any
     * @param string $edit_reason reason for performing update on wiki page
     * @param array $missing_fields fields missing from the request that might
     *  be needed to perform edit
     * @param string $read_address url base addressed to use
     *  in performing some wiki substitutions to generate a html page from a
     *  wiki page.
     * @param array $additional_substitutions additional preg_replace
     *  substitutions to make in going from wiki page to html
     */
    private function editWiki(&$data, $user_id, $group_id, $group, $page_id,
        $page_name, $page, $page_defaults, $sub_path, $edit_reason,
        $missing_fields, $read_address, $additional_substitutions)
    {
        if (empty($data["CAN_EDIT"])) {
            return;
        }
        $parent = $this->parent;
        $group_model = $parent->model("group");
        if (isset($_REQUEST['caret']) &&
           isset($_REQUEST['scroll_top'])
                && !isset($page)) {
            $caret = $parent->clean($_REQUEST['caret'],
                'int');
            $scroll_top = $parent->clean($_REQUEST['scroll_top'],
                'int');
            $data['SCRIPT'] .= "wiki = elt('wiki-page');" .
                "if (wiki != null) { " .
                "   if (wiki.setSelectionRange) { " .
                "       wiki.focus();" .
                "       wiki.setSelectionRange($caret, $caret);".
                "   } ".
                "   wiki.scrollTop = $scroll_top;" .
                "}";
        }
        $data["MODE"] = "edit";
        if ($data["SHARE_WALL_EDIT"]) {
            $data["MODE"] = "read";
            $_REQUEST['arg'] = 'read';
        }
        $page_info = $group_model->getPageInfoByName($group_id,
            $page_name, $data['CURRENT_LOCALE_TAG'], 'edit');
        /* if page not yet created than $page_info will be null
           so in the below $page_info['ID'] won't be set.
         */
        $upload_allowed = true;
        $preserve_fields = ['arg', 'page_name', 'resources', 'settings',
            'caret', 'scroll_top', 'sf'];
        if ($missing_fields) {
            return $parent->redirectWithMessage(
                tl("social_component_missing_fields"));
        } else if (isset($page_info['ID']) && !empty($_REQUEST['n'])
            && !isset($_REQUEST['resource_description'])) {
            $file_name = $parent->clean(urldecode($_REQUEST['n']),
                "file_name");
            $name_parts = pathinfo($file_name);
            if (!empty($name_parts['extension']) &&
                in_array($name_parts['extension'], ['csv', 'txt',
                    'tex', 'php', 'sql', 'html', 'java', 'py',
                    'pl', 'P', 'srt'])) {
                $extension = $name_parts['extension'];
                $data['RAW'] = !empty($_REQUEST['download']);
                $data['PAGE'] = $group_model->getPageResource(
                    $file_name, $group_id, $page_info['ID'], $sub_path,
                    $data['RAW']);
                if (empty($data['RAW']) && $name_parts['extension'] != 'csv') {
                    $data['PAGE'] = htmlentities($data['PAGE']);
                }
            } else {
                $data['PAGE'] = false;
            }
            if ($page !== null && $data['PAGE'] !== false) {
                $action = "wikiupdate_group=$group_id" .
                    "&page=" . $page_name . "&resource_name=" . $file_name;
                if (!$parent->checkCSRFTime(C\CSRF_TOKEN, $action)) {
                    $data['SCRIPT'] .= "doMessage('<h1 class=\"red\" >".
                        tl('social_component_wiki_edited_elsewhere').
                        "</h1>');";
                    return;
                }
                $success = $group_model->setPageResource($file_name,
                    $_REQUEST['page'], $group_id, $page_info['ID'],
                    $sub_path);
                $preserve_fields[] = 'n';
                if ($success) {
                    $preserve_fields[] = 'back_params';
                    return $parent->redirectWithMessage(
                        tl("social_component_resource_saved"),
                        $preserve_fields);
                } else {
                    return $parent->redirectWithMessage(
                        tl('social_component_resource_not_saved'),
                        $preserve_fields);
                }
            }
            $data['PAGE_ID'] = $page_info['ID'];
            $data['PAGE_NAME'] = $page_name;
            $data['RESOURCE_NAME'] = $file_name;
        } else {
            list($head_object, $page_data) = $parent->parsePageHeadVars(
                $page_info['PAGE'] ?? "", true);
            $is_currently_template = (!empty($head_object["page_type"]) &&
                $head_object["page_type"][0] == 't');
            $is_settings = (isset($_REQUEST['settings']) &&
                $_REQUEST['settings'] == 'true');
            if ($is_settings && $page === null &&
                $_SERVER['REQUEST_METHOD'] === 'POST') {
                $page = $page_data;
            }
            if (isset($page) || ($is_currently_template &&
                !empty($_REQUEST['page_type']))
                || isset($_REQUEST['default_sort'])) {
                $action = "wikiupdate_".
                    "group=" . $group_id . "&page=" . $page_name;
                if (!$parent->checkCSRFTime(C\CSRF_TOKEN, $action) &&
                    $head_object["page_type"] != "share") {
                    $data['SCRIPT'] .= "doMessage('<h1 class=\"red\" >".
                        tl('social_component_wiki_edited_elsewhere').
                        "</h1>');";
                    return;
                }
                $write_head = false;
                $head_vars = [];
                $page_types = array_keys($data['page_types']);
                $page_borders = array_keys($data['page_borders']);
                $set_path = false;
                foreach ($page_defaults as $key => $default) {
                    $head_vars[$key] = (isset($head_object[$key])) ?
                        $head_object[$key] : $default;
                    if (isset($_REQUEST[$key])) {
                        $head_vars[$key] =  trim(
                            $parent->clean($_REQUEST[$key], "string"));
                        switch ($key) {
                            case 'page_type':
                                if (!in_array($head_vars[$key], $page_types) &&
                                    ($head_vars[$key][0] != 't' ||
                                    !is_numeric(substr($head_vars[$key],1))) ) {
                                    $head_vars[$key] = $default;
                                }
                                break;
                            case 'page_borders':
                                if (!in_array($head_vars[$key],
                                    $page_borders)) {
                                    $head_vars[$key] = $default;
                                }
                                break;
                            case 'alternative_path':
                                if (!is_dir($head_vars[$key]) &&
                                    !empty($head_vars[$key])) {
                                    $head_vars[$key] = $default;
                                } else if (!empty($_SESSION['USER_ID'])
                                    && $_SESSION['USER_ID'] == C\ROOT_ID) {
                                    $set_path = true;
                                }
                                break;
                            case 'default_sort':
                                if (empty($page) &&
                                    !isset($page_info['PAGE'])) {
                                    break;
                                }
                                if (in_array($head_vars[$key],
                                    ['name', 'size', 'modified'])) {
                                    if (isset($page_info['PAGE'])) {
                                        if (!isset($page)) {
                                            $page_parts =
                                                explode("END_HEAD_VARS",
                                                $page_info['PAGE']);
                                            $page = isset($page_parts[1]) ?
                                                $page_parts[1] : $page_parts[0];
                                        }
                                    }
                                    $new_key = 'a' . $head_vars[$key];
                                    if (isset($head_object['default_sort'])) {
                                        set_error_handler(null);
                                        $head_object['default_sort'] =
                                            @unserialize(L\webdecode(
                                            $head_object['default_sort']));
                                        set_error_handler(C\NS_CONFIGS .
                                            "yioop_error_handler");
                                    } else {
                                        $head_object['default_sort'] = [];
                                    }
                                    $sort_path = empty($sub_path) ? "." :
                                        $sub_path;
                                    $sort_path = rtrim($sort_path, '/');
                                    $direction = '_asc';
                                    if (empty($head_object['default_sort'][
                                        $sort_path]) ||
                                        $head_object['default_sort'][$sort_path]
                                        == $new_key) {
                                        $direction = '_desc';
                                        $new_key = 'r' . $head_vars[$key];
                                    }
                                    $head_object['default_sort'][$sort_path] =
                                        $new_key;
                                    $folder_hash_id = L\crawlHash(
                                        $page_info['ID'] . $sub_path);
                                    $_SESSION['media_sorts'][$folder_hash_id] =
                                        $head_vars[$key] . $direction;
                                    $head_vars[$key] = L\webencode(serialize(
                                        $head_object['default_sort']));
                                    $edit_reason = "Change resource sort";
                                    $write_head = true;
                                } else {
                                    $head_vars[$key] = $default;
                                }
                                break;
                            default:
                                $head_vars[$key] =
                                    trim(preg_replace("/\n+/", "\n",
                                    $head_vars[$key]));
                        }
                        if ($head_vars[$key] != $default) {
                            $write_head = true;
                        }
                    } else if ($key == 'toc') {
                        if (isset($_REQUEST['title'])) {
                            $head_vars[$key] = false;
                        } else {
                            $head_vars[$key] == true;
                        }
                    } else if ($key == 'update_description') {
                        $head_vars[$key] =
                            isset($_REQUEST['update_description']);
                    }
                }
                $head_string = "";
                foreach ($page_defaults as $key => $default) {
                    $head_string .= urlencode($key) . "=" .
                        urlencode($head_vars[$key]) . "\n\n";
                }
                if (is_array($page)) { //template case
                    $page = base64_encode(serialize($page));
                }
                if (!empty($page) || (!empty($head_vars['page_type']) &&
                    $head_vars['page_type'] != 'standard')) {
                    $page = $head_string . "END_HEAD_VARS" . $page;
                }
                $page_info = (empty($page_info)) ? [] : $page_info;
                $page_info['ID'] = $group_model->setPageName($user_id,
                    $group_id, $page_name, $page,
                    $data['CURRENT_LOCALE_TAG'], $edit_reason,
                    tl('social_component_page_created', $page_name),
                    tl('social_component_page_discuss_here'),
                    $read_address, $additional_substitutions);
                $preserve_fields[] = 'n';
                if (empty($page_info['ID'])) {
                    return $parent->redirectWithMessage(
                        tl('social_component_page_not_saved'),
                        $preserve_fields);
                }
                if ($set_path && !empty($page_info['ID'])) {
                    $tmp = $group_model->getGroupPageResourcesFolders(
                        $group_id, $page_info['ID'], "", true, false);
                    if (isset($tmp[1])) {
                        list($resource_path, $thumb_path,) = $tmp;
                        if (!empty($head_vars['alternative_path'])) {
                            $parent->web_site->filePutContents(
                                "$resource_path/redirect.txt",
                                $head_vars['alternative_path']);
                        } else if (file_exists(
                            "$resource_path/redirect.txt") ) {
                            unlink("$resource_path/redirect.txt");
                        }
                    }
                }
                if (!isset($_FILES['page_resource']['name']) ||
                    $_FILES['page_resource']['name'] == "") {
                    return $parent->redirectWithMessage(
                        tl("social_component_page_saved"), $preserve_fields);
                }
            }
        }
        if (isset($_REQUEST['empty_clip'])) {
            $upload_allowed = false;
            if ($group_model->emptyClipFolder($user_id)) {
                return $parent->redirectWithMessage(
                    tl('social_component_clipboard_emptied'),
                    $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_clipboard_not_emptied'),
                    $preserve_fields);
            }
        } else if (isset($_REQUEST['paste_all'])) {
            $upload_allowed = false;
            if ($group_model->pasteAllClipFolder($user_id, $group_id,
                $page_info['ID'], $sub_path)) {
                return $parent->redirectWithMessage(
                    tl('social_component_paste_all_success'), $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_paste_all_failed'), $preserve_fields);
            }
        } else if (isset($_REQUEST['paste'])) {
            $resource_name = $parent->clean($_REQUEST['paste'],
                "string");
            $upload_allowed = false;
            if (!$group_model->pasteFromClipFolder(
                $user_id, $resource_name, $group_id,
                $page_info['ID'], $sub_path)) {
                return $parent->redirectWithMessage(
                    tl('social_component_paste_fail'), $preserve_fields);
            }
            return $parent->redirectWithMessage(
                tl('social_component_paste_success'), $preserve_fields);
        } else if (isset($_REQUEST['clip_copy'])) {
            $resource_name = $parent->clean($_REQUEST['clip_copy'],
                "string");
            $upload_allowed = false;
            if (!$group_model->copyResourceToClipFolder(
                $user_id, $resource_name, $group_id,
                $page_info['ID'], $sub_path)) {
                return $parent->redirectWithMessage(
                    tl('social_component_copy_fail'), $preserve_fields);
            }
            return $parent->redirectWithMessage(
                tl('social_component_copy_success'), $preserve_fields);
        } else if (isset($_REQUEST['clip_cut'])) {
            $upload_allowed = false;
            $resource_name = $parent->clean($_REQUEST['clip_cut'],
                "string");
            if (!$group_model->moveResourceToClipFolder(
                $user_id, $resource_name, $group_id,
                $page_info['ID'], $sub_path)) {
                return $parent->redirectWithMessage(
                    tl('social_component_clip_cut_fail'), $preserve_fields);
            }
            $group_model->versionGroupPage($user_id, $page_info['ID'],
                tl('social_component_clip_cut_success'));
            $_REQUEST['reset_detail'] = "true";
            return $parent->redirectWithMessage(
                tl('social_component_cut_success'), array_merge(
                    ["reset_detail"], $preserve_fields));
        } else if (isset($_REQUEST['extract'])) {
            $resource_name = $parent->clean($_REQUEST['extract'],
                "string");
            $upload_allowed = false;
            if (isset($page_info['ID']) &&
                $group_model->extractResource($resource_name,
                $group_id, $page_info['ID'], $sub_path)) {
                $group_model->versionGroupPage($user_id, $page_info['ID'],
                    tl('social_component_resource_extracted'));
                return $parent->redirectWithMessage(
                    tl('social_component_resource_extracted'),
                    $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_resource_not_extracted'),
                    $preserve_fields);
            }
        } else if (!empty($_REQUEST['new_resource_name']) &&
            !empty($_REQUEST['old_resource_name'])) {
            $upload_allowed = false;
            $old_resource_name = $parent->clean(
                $_REQUEST['old_resource_name'], "file_name");
            $new_resource_name = $parent->clean(
                $_REQUEST['new_resource_name'], "file_name");
            if (isset($page_info['ID']) &&
                $group_model->renameResource($old_resource_name,
                    $new_resource_name, $group_id,
                    $page_info['ID'], $sub_path)) {
                $group_model->versionGroupPage($user_id, $page_info['ID'],
                    tl('social_component_resource_renamed'));
                return $parent->redirectWithMessage(
                    tl('social_component_resource_renamed'),
                    $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_resource_not_renamed'),
                    $preserve_fields);
            }
        } else if (isset($_REQUEST['resource_description'])) {
            $resource_description = $parent->clean(
                $_REQUEST['resource_description'], "string");
            if (!($resource_name = $parent->clean(urldecode($_REQUEST['n']??""),
                "file_name"))) {
                return $parent->redirectWithMessage(
                    tl('social_component_resource_description_file_error'),
                    $preserve_fields);
            }
            $success = $group_model->setResourceDescription($resource_name,
                $resource_description, $group_id, $page_info['ID'],
                $sub_path ?? "");
            if ($success) {
                return $parent->redirectWithMessage(
                    tl('social_component_resource_description_saved'),
                    $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_resource_description_error'),
                    $preserve_fields);
            }
        } else if (isset($_REQUEST['resource_actions']) &&
            in_array($_REQUEST['resource_actions'],
            ['new-folder', 'new-text-file', 'new-csv-file']) &&
            !empty($page_info['ID'])) {
            if ($group_model->newResource($_REQUEST['resource_actions'],
                $group_id, $page_info['ID'], $sub_path)) {
                $group_model->versionGroupPage($user_id, $page_info['ID'],
                    tl('social_component_resource_created'));
                return $parent->redirectWithMessage(
                    tl('social_component_resource_created'),
                    $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_resource_not_created'),
                    $preserve_fields);
            }
        }
        if ($upload_allowed && !empty($_FILES['page_resource']['name'])) {
            if (!isset($page_info['ID'])) {
                $_FILES = [];
                return $parent->redirectWithMessage(
                    tl('social_component_resource_save_first'),
                    $preserve_fields);
            }
            $result = $this->handleResourceUploads(
                $group_id, $page_info['ID'], $sub_path);
            if ($result == self::UPLOAD_SUCCESS) {
                //we re-parse page so resources parsed
                if (isset($page) && isset($edit_reason)) {
                    $group_model->setPageName($user_id,
                        $group_id, $page_name, $page,
                        $data['CURRENT_LOCALE_TAG'], $edit_reason,
                        tl('social_component_page_created',
                        $page_name),
                        tl('social_component_page_discuss_here'),
                        $read_address, $additional_substitutions);
                }
                return $parent->redirectWithMessage(
                    tl('social_component_resource_uploaded'), $preserve_fields);
            } else {
                return $parent->redirectWithMessage(
                    tl('social_component_upload_error'), $preserve_fields);
            }
        }
        if (isset($page_info['ID'])) {
            $create = ($user_id == C\PUBLIC_USER_ID) ? false : true;
            $data['RESOURCES_INFO'] =
                $group_model->getGroupPageResourceUrls($group_id,
                $page_info['ID'], $sub_path, $create);
            if ($user_id != C\PUBLIC_USER_ID) {
                $data['CLIPBOARD_INFO'] =
                    $group_model->getClipboardResourceNames($user_id);
            }
        } else {
            $data['RESOURCES_INFO'] = [];
        }
    }
    /**
     *
     *
     * @param array &$data array of field variables for view will be modified
     *  by this function
     * @param int $group_id id of group wiki page belongs to
     * @param int $page_id id of wiki page
     * @param string $sub_path sub-resource folder that is being used, if any,
     *  that resource is from
     */
    public function mediaWikiDetail(&$data, $group_id, $page_id, $sub_path = "")
    {
        if (!isset($page_id)) {
            return;
        }
        $parent = $this->parent;
        $group_model = $parent->model("group");
        if (empty($_REQUEST['n'])) {
            $sub_parts = explode("/", $sub_path);
            $media_name = array_pop($sub_parts);
            $sub_path = implode("/", $sub_parts);
        } else {
            $media_name = $parent->clean($_REQUEST['n'], "file_name");
        }
        $page_info = $group_model->getPageInfoByPageId($page_id);
        $data['PAGE_NAME'] = htmlentities($page_info['PAGE_NAME'] ?? "");
        $page_info = $group_model->getPageInfoByName($group_id,
            $page_info['PAGE_NAME'] ?? "", $data['CURRENT_LOCALE_TAG'], 'edit');
        $data['HEAD'] = $parent->parsePageHeadVars($page_info['PAGE'] ?? "");
        $resources_info = $group_model->getGroupPageResourceUrls(
            $group_id, $page_id, $sub_path);
        $data['ORIGINAL_URL_PREFIX'] = $resources_info['url_prefix'];
        $resources = $resources_info['resources'] ?? "";
        $num_resources = (is_array($resources)) ? count($resources) : 0;
        for ($i = 0; $i < $num_resources; $i++) {
            if ($resources[$i]['name'] == $media_name) {
                break;
            }
        }
        if ($i == $num_resources) {
            $parent->web_site->header("HTTP/1.0 404 Not Found");
            $data["MEDIA_NAME"] = $media_name;
            $parent->displayView("nocache", $data);
            \seekquarry\yioop\library\webExit(); //bail
        }
        $data["RESOURCE_INFO"] = $resources[$i];
        $thumb_folder = $resources_info['thumb_folder'] ?? "";
        $description_file = $thumb_folder ."/$media_name.txt";
        $data['RESOURCE_DESCRIPTION'] = $group_model->getResourceDescription(
                $media_name, $group_id, $page_id, $sub_path);
        $data['RESOURCE_DESCRIPTION'] = (empty($data['RESOURCE_DESCRIPTION'])) ?
            tl('social_component_media_no_description') :
            $data['RESOURCE_DESCRIPTION'];
        $base_url = htmlentities(B\wikiUrl($data['PAGE_NAME'] , true,
            $data['CONTROLLER'], $group_id));
        if (isset($_SESSION['USER_ID']) && intval($_SESSION['USER_ID']) > 0) {
            $user_id = $_SESSION['USER_ID'];
            $data['ADMIN'] = 1;
        } else {
            $user_id = C\PUBLIC_USER_ID;
        }
        $csrf_token = $this->parent->generateCSRFToken(
            $user_id);
        if (!empty($data['ADMIN'])) {
            $base_url .= C\CSRF_TOKEN . "=". $csrf_token;
        }
        $folder_prefix = $base_url . "&amp;";
        $folder_prefix .= "page_id=". $page_id;
        $data['ROOT_LINK'] = $folder_prefix;
        if (!empty($data['SUB_PATH'])) {
             $folder_prefix .= "&amp;sf=" . urlencode($data['SUB_PATH']);
        }
        $data['FOLDER_PREFIX'] = $folder_prefix;
        $url_prefix = $folder_prefix . "&amp;arg=media";
        $data['MEDIA_NAME'] = $media_name;
        $page_string = "";
        $data['URL_PREFIX'] = $url_prefix;
        $data['THUMB_PREFIX'] = $resources_info['thumb_prefix'];
        $data['ATHUMB_PREFIX'] = $resources_info['athumb_prefix'];
        $data['DEFAULT_THUMB_URL'] = C\SHORT_BASE_URL .
            $resources_info['default_thumb'];
        $data['DEFAULT_EDITABLE_THUMB_URL'] = C\SHORT_BASE_URL .
            $resources_info['default_editable_thumb'];
        $data['DEFAULT_FOLDER_THUMB_URL'] = C\SHORT_BASE_URL .
            $resources_info['default_folder_thumb'];
        $data['EDIT'] = ($_REQUEST['arg'] == "media-detail-edit") ?
            true : false;
        $data["MODE"] = "media-detail";
        $data['VIEW'] = "mediadetail";
    }
    /**
     * Used to set up the partially processed wiki page, before media inserted,
     * needed to display a single media item on a media list. The name of
     * the media item to be display is expected to come from $_REQUEST['n'].
     *
     * @param array &$data array of field variables for view will be modified
     *  by this function
     * @param int $group_id id of group wiki page belongs to
     * @param int $page_id id of wiki page
     * @param string $sub_path sub-resource folder that is being used, if any,
     *  to get resources from
     */
    public function mediaWiki(&$data, $group_id, $page_id, $sub_path="")
    {
        if (!isset($page_id) || !isset($_REQUEST['n'])) {
            return;
        }
        $parent = $this->parent;
        $group_model = $parent->model("group");
        $media_name = $parent->clean($_REQUEST['n'], "file_name");
        $page_info = $group_model->getPageInfoByPageId($page_id);
        $data["DISCUSS_THREAD"] = $page_info["DISCUSS_THREAD"] ?? "";
        $data['SCRIPT'] = $data['SCRIPT'] ?? "";
        $data['PAGE_NAME'] = htmlentities($page_info['PAGE_NAME'] ?? "");
        $page_info = $group_model->getPageInfoByName($group_id,
            $page_info['PAGE_NAME'] ?? "", $data['CURRENT_LOCALE_TAG'], 'edit');
        $data['RESOURCES_INFO'] = $group_model->getGroupPageResourceUrls(
            $group_id, $page_id, $sub_path);
        $data['HEAD'] = $parent->parsePageHeadVars($page_info['PAGE'] ?? "");
        $this->initUserResourcePreferences($data);
        $resources = $data['RESOURCES_INFO']['resources'] ?? "";
        $num_resources = (is_array($resources)) ? count($resources) : 0;
        for ($i = 0; $i < $num_resources; $i++) {
            if ($resources[$i]['name'] == $media_name) {
                break;
            }
        }
        if ($i == $num_resources) {
            $parent->web_site->header("HTTP/1.0 404 Not Found");
            $data["MEDIA_NAME"] = $media_name;
            $parent->displayView("nocache", $data);
            \seekquarry\yioop\library\webExit(); //bail
        }
        $current_resource = $resources[$i];
        $is_static = ($data['CONTROLLER'] == 'static') ? true : false;
        $base_url = htmlentities(B\wikiUrl($data['PAGE_NAME'] , true,
            $data['CONTROLLER'], $group_id));
        if (isset($_SESSION['USER_ID']) && intval($_SESSION['USER_ID']) > 0) {
            $user_id = $_SESSION['USER_ID'];
            $data['ADMIN'] = 1;
        } else {
            $user_id = C\PUBLIC_USER_ID;
        }
        $csrf_token = $this->parent->generateCSRFToken(
            $user_id);
        if (!empty($data['ADMIN'])) {
            $base_url .= C\CSRF_TOKEN . "=". $csrf_token;
        }
        $folder_prefix = ($is_static) ? $base_url : $base_url . "&amp;";
        $folder_prefix .= "page_id=". $page_id;
        $data['ROOT_LINK'] = $folder_prefix;
        if (!empty($data['SUB_PATH'])) {
             $folder_prefix .= "&amp;sf=" . urlencode($data['SUB_PATH']);
        }
        $url_prefix = $folder_prefix . "&amp;arg=media";
        $mime_type = L\mimeType($media_name, true);
        $prev_name = ($i < $num_resources &&
            isset($resources[$i - 1]['name'])) ?
            $resources[$i - 1]['name'] : false;
        $next_name = (isset($resources[$i + 1]['name'])) ?
            $resources[$i + 1]['name'] : false;
        $name_parts = pathinfo($media_name);
        $file_name = $name_parts['filename'];
        $data['MEDIA_NAME'] = $media_name;
        $view = $parent->view($data['VIEW']);
        $page_string = "";
        $data['URL_PREFIX'] = $url_prefix;
        if (!empty($prev_name)) {
            $data['PREV_LINK'] = "$url_prefix&amp;n=" . urlencode($prev_name);
            $prev_link = $data['PREV_LINK'];
            if (!in_array($mime_type, ["application/epub+zip",
                "application/pdf", 'video/mp4', 'video/m4v'])) {
                $data['SCRIPT'] .= 'leftSwipe(document, function(evt) {'.
                    'window.location="'.$prev_link.'";})'."\n";
            }
        }
        if (!empty($next_name)) {
            $data['NEXT_LINK'] = "$url_prefix&amp;n=" . urlencode($next_name);
            $data['NEXT_INDEX'] = $i+1;
            $next_link = $data['NEXT_LINK'];
            if (!in_array($mime_type, ["application/epub+zip",
                "application/pdf", 'video/mp4', 'video/m4v'])) {
                $data['SCRIPT'] .= 'rightSwipe(document, function(evt) {'.
                    'window.location="'.$next_link.'";})'."\n";
            }
        }
        if (in_array($mime_type, ['video/mp4', 'video/m4v'])) {
            $current_url = $_SERVER['REQUEST_URI'];
            $current_url = substr($current_url, strlen(C\SHORT_BASE_URL));
            $current_url = C\BASE_URL . $current_url;
            $current_url = preg_replace("/". C\CSRF_TOKEN .
                "\=[^\/\&]+(\/|\&)/", "", $current_url);
            $resource_url = $group_model->getGroupPageResourceUrl($csrf_token,
                $group_id, $page_id, $media_name, $data['SUB_PATH'] ?? "");
            $resource_url = substr($resource_url, strlen(C\SHORT_BASE_URL));
            $resource_url = C\BASE_URL . $resource_url;
            $resource_url = preg_replace("/". C\CSRF_TOKEN .
                "\=[^\/\&]+/", "-", $resource_url);
            $thumb_url = "";
            if (C\REDIRECTS_ON) {
                if (!empty($current_resource['has_animated_thumb'])) {
                    $thumb_url = str_replace("wd/resources", "wd/athumbs",
                        $resource_url);
                } else if (!empty($current_resource['has_thumb'])) {
                    $thumb_url = str_replace("wd/resources", "wd/thumbs",
                        $resource_url);
                }
            } else {
                if (!empty($current_resource['has_animated_thumb'])) {
                    $thumb_url = $resource_url . "&amp;t=athumbs";
                } else if (!empty($current_resource['has_thumb'])) {
                    $thumb_url = $resource_url . "&amp;t=thumbs";
                }
            }
            list($folder, ) = $group_model->getGroupPageResourcesFolders(
                $group_id, $page_id, $data['SUB_PATH'] ?? "");
            $media_path = "$folder/$media_name";
            $pub_date = filemtime($media_path);
            $additional_metas =
                "\n<meta property='og:type' content='video' >\n" .
                "<meta property='og:url' content='$current_url' >\n" .
                "<meta property='og:title' content='$media_name' >\n" .
                "<meta property='video:release_date' content='" .
                    date("c", $pub_date) . "' >\n";
            if (!empty($thumb_url)) {
                $additional_metas .=
                    "<meta property='og:image' content='$thumb_url' >\n";
            }
            if (C\nsdefined("SITE_NAME")) {
                $additional_metas .=
                    "<meta property='og:site_name' content='". C\SITE_NAME .
                    "' >\n";
            }
            if (C\nsdefined("FFMPEG")) {
                $ffprobe = str_replace("ffmpeg", "ffprobe", C\FFMPEG);
                $probe_exec = "$ffprobe -v error -show_entries ".
                    "stream=width,height,duration -of ".
                    "default=noprint_wrappers=1 \"$media_path\"";
                exec($probe_exec, $probe_output);
                if (!empty($probe_output[2])) {
                    preg_match('/\=(\d+)/', $probe_output[0], $w_matches);
                    $width = $w_matches[1];
                    preg_match('/\=(\d+)/', $probe_output[1], $h_matches);
                    $height = $h_matches[1];
                    preg_match('/\=(\d+(\.\d*)?)/', $probe_output[2],
                        $d_matches);
                    $duration = $d_matches[1];
                    $additional_metas .=
                        "<meta property='og:video:width' content='$width' >\n".
                        "<meta property='og:video:height' ".
                        "content='$height' >\n" .
                        "<meta property='video:duration' " .
                        "content='$duration' >";
                }
            }
            $description = $group_model->getResourceDescription(
                    $media_name, $group_id, $page_id, $data['SUB_PATH'] ?? "");
            if (!empty($description)) {
                $description = htmlentities(strip_tags($description));
                $additional_metas .= "\n<meta property='og:description' " .
                    "content='$description' >\n";
            }
            $view->head_objects['additional_metas'] = $additional_metas;
        }
        $page_string .= "<div class='media-container'>";
        if (!empty($sub_path)) {
            $page_string .= "((resource:$media_name|$sub_path".
                "|$file_name ))";
        } else {
            $page_string .= "((resource:$media_name|$file_name ))";
        }
        $page_string .= "</div>";
        $include_charts_and_spreadsheets = ($mime_type == 'text/csv') ?
            true : false;
        $data["PAGE"] = $group_model->insertResourcesParsePage(
            $group_id, $page_id, $data['CURRENT_LOCALE_TAG'],
            $page_string, $csrf_token, $data['CONTROLLER'],
            $include_charts_and_spreadsheets);
        if (substr($mime_type, 0, 4) == 'text') {
            $this->parent->recordViewSession($page_id, $sub_path, $media_name);
        }
        if ($mime_type == "text/csv" && empty($data['RAW'])) {
            $data['INCLUDE_SCRIPTS'][] = 'spreadsheet';
            $data['SPREADSHEET'] = true;
        }
        $data["PAGE_ID"] = $page_id;
        $resource_id = unpack('n', md5($group_id . $page_id .
            $data['RESOURCES_INFO']['thumb_folder'] . "/" .
            $_REQUEST['n'], true))[1];
        $parent->model("impression")->add($user_id, $resource_id,
            C\RESOURCE_IMPRESSION);
    }
    /**
     * Used to initialize arrays for dropdowns in WikiElement as well
     * as various arrays for cleaning request variables
     *
     * @param string $controller_name used to set up variables for view elements
     *  should be either admin, api, or group depending on which controller
     *  is being used to handle wiki interaction
     * @param string $base_url to use in creating link targets
     */
    private function initCommonWikiArrays($controller_name, $base_url)
    {
        $parent = $this->parent;
        $data = [];
        $data["CONTROLLER"] = $controller_name;
        $data["ELEMENT"] = "wiki";
        $data["VIEW"] = "group";
        $data["SCRIPT"] = "";
        $data["INCLUDE_STYLES"] = ["editor"];
        $locale_tag = L\getLocaleTag();
        $data['CURRENT_LOCALE_TAG'] = $locale_tag;
        $sub_path = "";
        if (!empty($_REQUEST['page_name'])) {
            $name_parts = explode("/", $_REQUEST['page_name']);
            if (count($name_parts) > 1) {
                $_REQUEST['page_name'] = array_shift($name_parts);
                $sub_path = $parent->clean(implode("/", $name_parts),
                    'string');
                $sub_path = str_replace("..", "", $sub_path);
                $sub_path = str_replace("/./", "/", $sub_path);
                $data['SUB_PATH'] = htmlentities($sub_path);
            }
        }
        if (!empty($_REQUEST['sf'])) {
            $sub_path = $parent->clean($_REQUEST['sf'], 'string');
            $sub_path = str_replace("..", "", $sub_path);
            $sub_path = str_replace("/./", "/", $sub_path);
            $data['SUB_PATH'] = htmlentities($sub_path);
        }
        if (!empty($_REQUEST['reset_detail'])) {
            if ($_REQUEST['reset_detail'] == 'true') {
                $data['RESET_DETAIL'] = true;
            }
        }
        $data['ORIGINAL_SUB_PATH'] = $sub_path;
        if ((isset($_REQUEST['c'])) && $_REQUEST['c'] == "api") {
            //wiki help request
            $data['MODE'] = 'api';
            $data['VIEW'] = 'api';
        } else {
            $data["MODE"] = "read";
            // additional feed data on page_and_feedback page
            if (!empty($_REQUEST['f']) && $_REQUEST['f'] == "api") {
                $data['VIEW'] = 'api';
            }
        }
        $data['page_types'] = [
            "standard" => tl('social_component_standard_page'),
            "page_and_feedback" => tl('social_component_page_and_feedback'),
            "page_alias" => tl('social_component_page_alias'),
            "media_list" => tl('social_component_media_list'),
            "presentation" => tl('social_component_presentation'),
            "url_shortener" => tl('social_component_url_shortener'),
            "share" => tl('social_component_share_wall')
        ];
        $data['page_borders'] = [
            "solid-border" => tl('social_component_solid'),
            "dashed-border" => tl('social_component_dashed'),
            "none" => tl('social_component_none')
        ];
        $page_themes = $parent->model('profile')->getThemeNames();
        $data['page_themes'] = array_merge(
            ["" => tl('social_component_no_auxiliary_theme')],
            array_combine($page_themes , $page_themes));
        $data['update_descriptions'] = [
            "no-lookup" => tl('social_component_no_lookup'),
            "files-only" => tl('social_component_files_only'),
            "folders-only" => tl('social_component_folders_only'),
            "files-and-folders" => tl('social_component_files_and_folders')
        ];
        $data['resource_actions'] = [
            tl('social_component_actions') => "",
            "new-folder" => tl('social_component_new_folder'),
            "new-text-file" => tl('social_component_new_text_file'),
            "new-csv-file" => tl('social_component_new_csv_file'),
        ];
        $data['share_page_expires'] = [
            C\FOREVER => tl('social_component_never'),
            C\ONE_HOUR => tl('social_component_one_hour'),
            C\ONE_DAY => tl('social_component_one_day'),
            C\ONE_MONTH => tl('social_component_one_month'),
        ];
        $data['sort_fields'] = [
            tl('social_component_sort_order') => "",
            "name_asc" => tl('social_component_name_ascending'),
            "name_desc" => tl('social_component_name_descending'),
            "modified_asc" => tl('social_component_date_ascending'),
            "modified_desc" => tl('social_component_date_descending'),
            "size_asc" => tl('social_component_size_ascending'),
            "size_desc" => tl('social_component_size_descending'),
        ];
        $search_form = <<<EOD
<form method="get" action='$base_url' class="search-box $2-search-box" >
<div class="search-box-inner"  tabindex="0">
<input type='hidden' name="its" value='$1' >
<input type='text'  name='q'  value="" placeholder='$3'
    title='$3' class='search-input' >
EOD;
        $search_form .=
            $parent->view("group")->helper("iconlink")->renderFormButton(
                "reset", "reset", true, "search-reset-button") .
            $parent->view("group")->helper("iconlink")->renderFormButton(
                "submit", "search", true, "search-button") . <<<EOD
</div>
</form>
EOD;
        $additional_substitutions[] = ['/{{\s*search\s*:\s*(.+?)\s*\|'.
            '\s*size\s*:\s*(.+?)\s*\|\s*placeholder\s*:\s*(.+?)}}/',
            $search_form];
        $clean_array = [
            "group_id" => "int",
            "page_name" => "string", //up to here are required fields
            "diff" => 'int',
            "diff1" => 'int',
            "diff2" => 'int',
            "edit_reason" => "string",
            "filter" => 'string',
            "group_name" => 'string',
            "limit" => 'int',
            "num" => 'int',
            "page" => "string",
            "page_id" => 'int',
            'page_theme' => 'string',
            'resource_description' => '',
            'resource_filter' => 'file_name',
            "revert" => 'int',
            "share_expires" => 'string',
            "share_wall_data" => 'string',
            "show" => 'int',
            "sort" => 'string',
        ];
        $strings_array = [
            "page_name" => C\TITLE_LEN,
            "page" => C\MAX_GROUP_PAGE_LEN,
            "edit_reason" => C\SHORT_TITLE_LEN,
            "filter" => C\SHORT_TITLE_LEN,
            "resource_filter" => C\SHORT_TITLE_LEN];
        $page_defaults = [
            'alternative_path' => '',
            'author' => '',
            'default_sort' => 'aname',
            'description' => '',
            'page_alias' => '',
            'page_border' => 'solid',
            'page_header' => '',
            'page_footer' => '',
            'page_theme' => '',
            'page_type' => 'standard',
            'properties' => '',
            'robots' => '',
            'share_expires' => C\FOREVER,
            'title' => '',
            'toc' => true,
            'url_shortener' => '',
            'update_description' => false
        ];
       /* Check if back params need to be set. Set them if required.
          the back params are usually sent when the wiki action is initiated
          from within an open help article.
        */
        $data["OTHER_BACK_URL"] = "";
        if (isset($_REQUEST['back_params']) &&
            ((isset($_REQUEST['arg']) && in_array(
                $parent->clean($_REQUEST['arg'],"string"), ['edit',
                'read'])) || (isset($_REQUEST['page_name'])))
                ) {
            $back_params_cleaned = $_REQUEST['back_params'];
            array_walk($back_params_cleaned, [$parent, 'clean']);
            foreach ($back_params_cleaned as
                    $back_param_key => $back_param_value) {
                $data['BACK_PARAMS']["back_params[$back_param_key]"]
                    = $back_param_value;
                $data["OTHER_BACK_URL"] .=
                    "&amp;back_params[$back_param_key]" . "=" .
                    $back_param_value;
            }
            $data['BACK_URL'] = http_build_query($back_params_cleaned);
        }
        return [$data, $sub_path, $additional_substitutions, $clean_array,
            $strings_array, $page_defaults];
    }
    /**
     * Used to create Javascript used to toggle a wiki page's settings control
     *
     * @param array &$data will contain in SCRIPT field necessary Javascript
     *  to pass to view.
     */
    private function initializeWikiPageToggle(&$data)
    {
        $init_toggle_settings = (empty($data['RESOURCE_NAME'])) ?
            'setDisplay("toggle-settings", true, "inline");': '';
        $toggle_settings_on = (empty($data['RESOURCE_NAME'])) ?
            'setDisplay("toggle-settings", true);': '';
        $toggle_settings_false = (empty($data['RESOURCE_NAME'])) ?
            'setDisplay("toggle-settings", false);': '';
        $toggle_settings_inline = (empty($data['RESOURCE_NAME'])) ?
            'setDisplay("toggle-settings", true, "inline");': '';
        $data['SCRIPT'] .= <<< EOD
            mode = '{$data['MODE']}';
            function toggleSettings()
            {
                var settings = elt('p-settings');
                settings.value = (settings.value == 'true')
                    ? 'false' : 'true';
                var value = (settings.value == 'true') ? true : false;
                var r_settings =elt('r-settings');
                if (r_settings && mode == 'edit') {
                    elt('r-settings').value = settings.value;
                }
                setDisplay('page-settings', value);
                var page_type = elt("page-type");
                var cur_type = page_type.options[
                    page_type.selectedIndex].value;
                if (cur_type == "media_list" && mode == 'edit') {
                    setDisplay('save-container', value);
                }
                toggleClass("settings-toggle-button","back-gray");
            }
            ptype = document.getElementById("page-type");
            is_media_list = ('media_list'=='{$data['current_page_type']}');
            is_settings = {$data['settings']};
            is_page_alias = ('page_alias'=='{$data['current_page_type']}');
            is_url_shortener =
                ('url_shortener'=='{$data['current_page_type']}');
            is_share = ('share'=='{$data['current_page_type']}');
            setDisplay('page-settings', is_settings || is_page_alias ||
                is_share || is_url_shortener);
            setDisplay("page-container", !is_media_list && !is_page_alias &&
                !is_url_shortener && !is_share);
            setDisplay("non-alias-type", !is_page_alias && !is_url_shortener &&
                !is_share);
            setDisplay("alias-type", is_page_alias);
            setDisplay("shortener-container", is_url_shortener);
            setDisplay("short-url-label", is_url_shortener);
            setDisplay("page-resources", !is_page_alias && !is_url_shortener &&
                !is_share);
            setDisplay("share-container", is_share);
            if (mode == 'edit') {
                setDisplay('save-container', !is_media_list || is_settings);
                setDisplay('resource-upload-form', is_media_list &&
                    !is_share);
            }
            $init_toggle_settings
            ptype.onchange = function() {
                var cur_type = ptype.options[ptype.selectedIndex].value;
                if (cur_type == "media_list") {
                    setDisplay("page-container", false);
                    $toggle_settings_on
                    setDisplay("non-alias-type", true);
                    setDisplay("alias-type", false);
                    setDisplay("shortener-container", false);
                    setDisplay("short-url-label", false);
                    setDisplay("share-container", false);
                    setDisplay("page-resources", true);
                    if (mode == 'edit') {
                        setDisplay("resource-upload-form", true);
                    }
                } else if (cur_type == "page_alias") {
                    $toggle_settings_false
                    setDisplay("page-container", false);
                    setDisplay("non-alias-type", false);
                    setDisplay("alias-type", true);
                    setDisplay("shortener-container", false);
                    setDisplay("short-url-label", false);
                    setDisplay("share-container", false);
                    setDisplay("page-resources", false);
                } else if (cur_type == "url_shortener") {
                     $toggle_settings_false
                     setDisplay("page-container", false);
                     setDisplay("non-alias-type", false);
                     setDisplay("alias-type", false);
                     setDisplay("shortener-container", true);
                     setDisplay("short-url-label", true);
                     setDisplay("share-container", false);
                     setDisplay("page-resources", false);
                     setDisplay("resource_msg", false);
                }  else if (cur_type == "share") {
                    $toggle_settings_false
                    setDisplay("page-container", false);
                    setDisplay("non-alias-type", false);
                    setDisplay("alias-type", false);
                    setDisplay("shortener-container", false);
                    setDisplay("short-url-label", false);
                    setDisplay("share-container", true);
                    setDisplay("page-resources", false);
                    setDisplay("resource_msg", false);
                } else {
                    setDisplay("page-container", true);
                    $toggle_settings_inline
                    setDisplay("non-alias-type", true);
                    setDisplay("alias-type", false);
                    setDisplay("shortener-container", false);
                    setDisplay("short-url-label", false);
                    setDisplay("share-container", false);
                    setDisplay("page-resources", true);
                    if (mode == 'edit') {
                        setDisplay("resource-upload-form", false);
                    }
                }
            }
EOD;
    }
}
ViewGit