<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    leader_board
 */

require_code('crud_module');

/**
 * Module page class.
 */
class Module_admin_leader_board extends Standard_crud_module
{
    protected $lang_type = 'LEADER_BOARD';
    protected $select_name = 'TITLE';
    protected $archive_entry_point = '_SEARCH:leader_board';
    protected $archive_label = 'VIEW_ARCHIVE';
    protected $permissions_require = 'high';
    protected $menu_label = 'LEADER_BOARDS';
    protected $table = 'leader_boards';
    protected $orderer = 'lb_title';
    protected $donext_entry_content_type = 'leader_board';
    protected $donext_category_content_type = null;
    protected $do_preview = false;

    /**
     * Find details of the module.
     *
     * @return ?array Map of module info (null: module is disabled)
     */
    public function info() : ?array
    {
        $info = [];
        $info['author'] = 'Patrick Schmalstig';
        $info['organisation'] = 'Composr';
        $info['hacked_by'] = null;
        $info['hack_version'] = null;
        $info['version'] = 1;
        $info['locked'] = false;
        $info['min_cms_version'] = 11.0;
        $info['addon'] = 'leader_board';
        return $info;
    }

    /**
     * Find entry-points available within this module.
     *
     * @param  boolean $check_perms Whether to check permissions
     * @param  ?MEMBER $member_id The member to check permissions as (null: current user)
     * @param  boolean $support_crosslinks Whether to allow cross links to other modules (identifiable via a full-page-link rather than a screen-name)
     * @param  boolean $be_deferential Whether to avoid any entry-point (or even return null to disable the page in the Sitemap) if we know another module, or page_group, is going to link to that entry-point. Note that "!" and "browse" entry points are automatically merged with container page nodes (likely called by page-groupings) as appropriate.
     * @return ?array A map of entry points (screen-name=>language-code/string or screen-name=>[language-code/string, icon-theme-image]) (null: disabled)
     */
    public function get_entry_points(bool $check_perms = true, ?int $member_id = null, bool $support_crosslinks = true, bool $be_deferential = false) : ?array
    {
        if (!addon_installed('leader_board') || !addon_installed('points')) {
            return null;
        }

        $ret = [
            'browse' => ['MANAGE_LEADER_BOARDS', 'menu/social/leader_board'],
        ];

        $ret += parent::get_entry_points();

        if ($member_id === null) {
            $member_id = get_member();
        }

        if (has_privilege($member_id, 'mass_import')) {
            $ret += [
                'predefined_content' => ['PREDEFINED_CONTENT', 'admin/import'],
            ];
        }

        return $ret;
    }

    public $title;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @param  boolean $top_level Whether this is running at the top level, prior to having sub-objects called
     * @param  ?ID_TEXT $type The screen type to consider for metadata purposes (null: read from environment)
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none)
     */
    public function pre_run(bool $top_level = true, ?string $type = null) : ?object
    {
        $error_msg = new Tempcode();
        if (!addon_installed__messaged('leader_board', $error_msg)) {
            return $error_msg;
        }
        if (!addon_installed__messaged('points', $error_msg)) {
            return $error_msg;
        }

        $type = get_param_string('type', 'browse');

        require_lang('leader_board');

        set_helper_panel_tutorial('tut_points');

        if ($type == 'predefined_content') {
        }

        if ($type == '_predefined_content') {
            breadcrumb_set_parents([['_SELF:_SELF:browse', do_lang_tempcode('MANAGE_LEADER_BOARDS')], ['_SELF:_SELF:predefined_content', do_lang_tempcode('PREDEFINED_CONTENT')]]);
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

        if ($type == 'predefined_content' || $type == '_predefined_content') {
            $this->title = get_screen_title('PREDEFINED_CONTENT');
        }

        return parent::pre_run($top_level);
    }

    /**
     * Standard crud_module run_start.
     *
     * @param  ID_TEXT $type The type of module execution
     * @return Tempcode The output of the run
     */
    public function run_start(string $type) : object
    {
        require_code('leader_board');

        $this->add_text = do_lang_tempcode('LEADER_BOARD_ALLOCATEHELP');

        $this->add_one_label = do_lang_tempcode('ADD_LEADER_BOARD');
        $this->edit_this_label = do_lang_tempcode('EDIT_THIS_LEADER_BOARD');
        $this->edit_one_label = do_lang_tempcode('EDIT_LEADER_BOARD');

        if ($type == 'browse') {
            return $this->browse();
        }
        if ($type == 'predefined_content') {
            return $this->predefined_content();
        }
        if ($type == '_predefined_content') {
            return $this->_predefined_content();
        }

        return new Tempcode();
    }

    /**
     * The do-next manager for before content management.
     *
     * @return Tempcode The UI
     */
    public function browse() : object
    {
        require_code('templates_donext');
        return do_next_manager(
            get_screen_title('MANAGE_LEADER_BOARDS'),
            comcode_lang_string('DOC_LEADER_BOARDS'),
            [
                ['admin/add', ['_SELF', ['type' => 'add'], '_SELF'], do_lang('ADD_LEADER_BOARD')],
                ['admin/edit', ['_SELF', ['type' => 'edit'], '_SELF'], do_lang('EDIT_LEADER_BOARD')],
                has_privilege(get_member(), 'mass_import') ? ['admin/install', ['_SELF', ['type' => 'predefined_content'], '_SELF'], do_lang('PREDEFINED_CONTENT')] : null,
            ],
            do_lang('MANAGE_LEADER_BOARDS')
        );
    }

    /**
     * Standard crud_module table function.
     *
     * @param  array $url_map Details to go to build_url for link to the next screen
     * @return array A pair: The choose table, Whether re-ordering is supported from this screen
     */
    public function create_selection_list_choose_table(array $url_map) : array
    {
        require_code('templates_results_table');

        $hr = [];
        $hr[] = do_lang_tempcode('TITLE');
        $hr[] = do_lang_tempcode('LEADER_BOARD_TYPE');
        $hr[] = do_lang_tempcode('LEADER_BOARD_SIZE');
        $hr[] = do_lang_tempcode('LEADER_BOARD_TIME_FRAME');
        $hr[] = do_lang_tempcode('ACTIONS');

        $current_ordering = get_param_string('sort', 'lb_title ASC', INPUT_FILTER_GET_COMPLEX);
        $sortables = [
            'lb_title' => do_lang_tempcode('TITLE'),
        ];
        list($sql_sort, $sort_order, $sortable) = process_sorting_params('leader_board', $current_ordering);

        $header_row = results_header_row($hr, $sortables, 'sort', $sortable . ' ' . $sort_order);

        $result_entries = new Tempcode();

        list($rows, $max_rows) = $this->get_entry_rows(false, $sql_sort);
        foreach ($rows as $row) {
            $edit_url = build_url($url_map + ['id' => $row['id']], '_SELF');

            $fr = [];
            $fr[] = protect_from_escaping(hyperlink(build_url(['page' => 'leader_board', 'type' => 'browse', 'id' => $row['id']], get_module_zone('leader_board')), $row['lb_title'], false, true));
            $fr[] = do_lang('LEADER_BOARD_TYPE_' . $row['lb_type']);
            $fr[] = integer_format($row['lb_member_count']);
            $fr[] = do_lang('LEADER_BOARD_TIME_FRAME_' . $row['lb_timeframe']);
            $fr[] = protect_from_escaping(hyperlink($edit_url, do_lang_tempcode('EDIT'), false, false, do_lang('EDIT') . ' #' . strval($row['id'])));

            $result_entries->attach(results_entry($fr, true));
        }

        return [results_table(do_lang($this->menu_label), get_param_integer('start', 0), 'start', either_param_integer('max', 20), 'max', $max_rows, $header_row, $result_entries, $sortables, $sortable, $sort_order), false];
    }

    /**
     * Get Tempcode for an adding form.
     *
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function get_form_fields_for_add()
    {
        return $this->get_form_fields();
    }

    /**
     * Get Tempcode for adding/editing form.
     *
     * @param  ?AUTO_LINK $id The ID of the leader-board (null: not added yet)
     * @param  SHORT_TEXT $title A title for this leader-board
     * @param  SHORT_TEXT $board_type The type of leader-board
     * @set holders earners
     * @param  integer $member_count The size of the leader-board
     * @param  SHORT_TEXT $timeframe The frequency of this leader-board
     * @set week month year
     * @param  BINARY $rolling Whether or not this leader-board generation time is rolling
     * @param  BINARY $include_staff Whether to include staff in the leader-board
     * @param  array $usergroups Only allow members in one or more defined usergroup IDs to be included in the leader-board (empty: allow all usergroups)
     * @param  BINARY $calculate_voting_power Whether this leader-board should calculate voting powers and control percentages and also display those
     * @return array A pair: The input fields, Hidden fields
     */
    public function get_form_fields(?int $id = null, string $title = '', string $board_type = 'holders', int $member_count = 10, string $timeframe = 'week', int $rolling = 0, int $include_staff = 0, array $usergroups = [], int $calculate_voting_power = 0) : array
    {
        require_code('leader_board');

        $fields = new Tempcode();
        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, true));

        // Only allow editing leader-board type if there are no results generated so far
        if ($id === null) {
            $can_edit_type = true;
        } else {
            $results = get_leader_board($id);
            $can_edit_type = (empty($results));
        }
        $radios = new Tempcode();
        $radios->attach(form_input_radio_entry('leader_board_type', 'holders', $board_type == 'holders', do_lang_tempcode('LEADER_BOARD_TYPE_holders'), null, '', !$can_edit_type));
        $radios->attach(form_input_radio_entry('leader_board_type', 'earners', $board_type == 'earners', do_lang_tempcode('LEADER_BOARD_TYPE_earners'), null, '', !$can_edit_type));
        $fields->attach(form_input_radio(do_lang_tempcode('LEADER_BOARD_TYPE'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_TYPE'), 'leader_board_type', $radios, true));

        $fields->attach(form_input_integer(do_lang_tempcode('LEADER_BOARD_SIZE'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_SIZE'), 'member_count', $member_count, true));

        $timeframes = new Tempcode();
        $timeframes->attach(form_input_list_entry('week', $timeframe == 'week', do_lang_tempcode('LEADER_BOARD_TIME_FRAME_week')));

        // TODO: #6001 month operations are extremely unreliable; this has been disabled until it can be fixed
        $timeframes->attach(form_input_list_entry('month', $timeframe == 'month', do_lang_tempcode('LEADER_BOARD_TIME_FRAME_month')));
        //require_code('comcode');
        //attach_message(comcode_to_tempcode('Monthly leader-boards have been temporarily disabled until they can be fixed. See [url="Tracker issue #6001"]https://composr.app/tracker/view.php?id=6001[/url]'), 'notice');

        $timeframes->attach(form_input_list_entry('year', $timeframe == 'year', do_lang_tempcode('LEADER_BOARD_TIME_FRAME_year')));
        $fields->attach(form_input_list(do_lang_tempcode('LEADER_BOARD_TIME_FRAME'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_TIME_FRAME'), 'timeframe', $timeframes));

        $radios2 = new Tempcode();
        $radios2->attach(form_input_radio_entry('rolling', '0', $rolling == 0, do_lang_tempcode('LEADER_BOARD_ROLLING_0'), null, ''));
        $radios2->attach(form_input_radio_entry('rolling', '1', $rolling == 1, do_lang_tempcode('LEADER_BOARD_ROLLING_1'), null, ''));
        $fields->attach(form_input_radio(do_lang_tempcode('LEADER_BOARD_ROLLING'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_ROLLING'), 'rolling', $radios2, true));

        $fields->attach(form_input_tick(do_lang_tempcode('LEADER_BOARD_SHOW_STAFF'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_SHOW_STAFF'), 'include_staff', $include_staff == 1));

        if (get_forum_type() == 'cns') {
            $usergroups_select = new Tempcode();
            $usergroups_select->attach(cns_create_multi_list_usergroups($usergroups, false));
            $fields->attach(form_input_multi_list(do_lang_tempcode('USERGROUPS'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_USERGROUPS'), 'usergroups', $usergroups_select));
            $fields->attach(form_input_tick(do_lang_tempcode('LEADER_BOARD_CALCULATE_VOTING_POWER'), do_lang_tempcode('DESCRIPTION_LEADER_BOARD_CALCULATE_VOTING_POWER'), 'calculate_voting_power', $calculate_voting_power == 1));
        }

        return [$fields, new Tempcode()];
    }

    /**
     * Standard crud_module list function.
     *
     * @return Tempcode The selection list
     */
    public function create_selection_list_entries() : object
    {
        $_m = $GLOBALS['SITE_DB']->query_select('leader_boards', ['id', 'lb_title'], [], 'ORDER BY lb_title');
        $entries = new Tempcode();
        foreach ($_m as $m) {
            $entries->attach(form_input_list_entry(strval($m['id']), false, $m['lb_title']));
        }

        return $entries;
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $id The entry being edited
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function fill_in_edit_form(string $id)
    {
        $m = $GLOBALS['SITE_DB']->query_select('leader_boards', ['*'], ['id' => intval($id)], '', 1);
        if (!array_key_exists(0, $m)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'leader_board'));
        }
        $r = $m[0];

        $g = $GLOBALS['SITE_DB']->query_select('leader_boards_groups', ['*'], ['lb_leader_board_id' => intval($id)]);
        $g = collapse_1d_complexity('lb_group', $g);

        $fields = $this->get_form_fields(intval($id), $r['lb_title'], $r['lb_type'], $r['lb_member_count'], $r['lb_timeframe'], $r['lb_rolling'], $r['lb_include_staff'], $g, $r['lb_calculate_voting_power']);

        return $fields;
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return array A pair: The entry added, description about usage
     */
    public function add_actualisation() : array
    {
        require_code('leader_board2');

        if (array_key_exists('usergroups', $_POST)) {
            $usergroups = $_POST['usergroups'];
        } else {
            $usergroups = [];
        }

        $id = add_leader_board(post_param_string('title'), post_param_string('leader_board_type'), post_param_integer('member_count'), post_param_string('timeframe'), post_param_integer('rolling', 0), post_param_integer('include_staff', 0), $usergroups, post_param_integer('calculate_voting_power', 0));

        return [strval($id), null];
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $id The entry being edited
     * @return ?Tempcode Description about usage (null: none)
     */
    public function edit_actualisation(string $id) : ?object
    {
        require_code('leader_board2');

        if (array_key_exists('usergroups', $_POST)) {
            $usergroups = $_POST['usergroups'];
        } else {
            $usergroups = [];
        }

        edit_leader_board(intval($id), post_param_string('title'), post_param_string('leader_board_type', null), post_param_integer('member_count'), post_param_string('timeframe'), post_param_integer('rolling', 0), post_param_integer('include_staff', 0), $usergroups, post_param_integer('calculate_voting_power', 0));

        return null;
    }

    /**
     * Standard crud_module delete actualiser.
     *
     * @param  ID_TEXT $id The entry being deleted
     */
    public function delete_actualisation(string $id)
    {
        require_code('leader_board2');

        delete_leader_board(intval($id));
    }

    /**
     * UI for install/uninstall of predefined content.
     *
     * @return Tempcode The UI
     */
    public function predefined_content() : object
    {
        require_code('content2');
        return predefined_content_changes_ui('leader_board', $this->title, build_url(['page' => '_SELF', 'type' => '_predefined_content'], '_SELF'));
    }

    /**
     * Actualise install/uninstall of predefined content.
     *
     * @return Tempcode The UI
     */
    public function _predefined_content() : object
    {
        require_code('content2');
        return predefined_content_changes_actualiser('leader_board', $this->title);
    }
}
