<?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    stats
 */

/**
 * Hook class.
 */
class Hook_admin_stats_comments extends CMSStatsProvider
{
    protected $comments_brackets;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->comments_brackets = [
            '0',
            '1',
            '2',
            '3-4',
            '5-9',
            '10-29',
            '30+',
        ];
    }

    /**
     * Find metadata about stats graphs that are provided by this stats hook.
     *
     * @param  boolean $for_kpi Whether this is for setting up a KPI
     * @return ?array Map of metadata (null: hook is disabled)
     */
    public function info(bool $for_kpi = false) : ?array
    {
        if (get_forum_type() != 'cns') {
            return null;
        }

        if (get_option('is_on_strong_forum_tie') == '0') {
            return null;
        }

        if (get_option('is_on_comments') == '0') {
            return null;
        }

        $forum = get_option('comments_forum_name');
        if (!is_integer($forum)) {
            $forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($forum);
            if ($forum_id === null) {
                return null;
            }
        }

        return [
            'comments' => [ // Average number
                'label' => do_lang_tempcode('COMMENTS'),
                'category' => 'feedback_and_engagement',
                'filters' => [
                    'comments__month_range' => new CMSStatsDateMonthRangeFilter('comments__month_range', do_lang_tempcode('DATE_RANGE'), null, $for_kpi),
                    'comments_tallies__content_type' => new CMSStatsListFilter('comments_tallies__content_type', do_lang_tempcode('CONTENT_TYPE'), $this->find_all_feedback_type_codes()),
                ],
                'pivot' => new CMSStatsDatePivot('comments__pivot', $this->get_date_pivots(!$for_kpi)),
                'support_kpis' => self::KPI_HIGH_IS_GOOD,
            ],
            'comments_tallies' => [ // Tally by content
                'label' => do_lang_tempcode('COMMENT_ENGAGEMENT'),
                'category' => 'feedback_and_engagement',
                'filters' => [
                    'comments_tallies__month_range' => new CMSStatsDateMonthRangeFilter('comments_tallies__month_range', do_lang_tempcode('DATE_RANGE'), null, $for_kpi),
                    'comments_tallies__content_type' => new CMSStatsListFilter('comments_tallies__content_type', do_lang_tempcode('CONTENT_TYPE'), $this->find_all_feedback_type_codes()),
                ],
                'pivot' => null,
            ],
        ];
    }

    /**
     * Preprocess raw data in the database into something we can efficiently draw graphs/conclusions from.
     *
     * @param  TIME $start_time Start timestamp
     * @param  TIME $end_time End timestamp
     * @param  array $data_buckets Map of data buckets; a map of bucket name to nested maps with the following maps in sequence: 'month', 'pivot', 'value' (then further map data) ; extended and returned by reference
     */
    public function preprocess_raw_data(int $start_time, int $end_time, array &$data_buckets)
    {
        require_code('temporal');

        $server_timezone = get_server_timezone();

        $feedback_type_codes = $this->find_all_feedback_type_codes();

        $max = 1000;
        $start = 0;

        $date_pivots = $this->get_date_pivots();

        $forum = get_option('comments_forum_name');
        if (!is_integer($forum)) {
            $forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($forum);
            if ($forum_id === null) {
                return;
            }
        } else {
            $forum_id = intval($forum);
        }

        do {
            $topics = $GLOBALS['FORUM_DB']->query_select('f_topics', ['id', 't_description', 't_cache_first_time', 't_cache_num_posts'], ['t_forum_id' => $forum_id], '', $max, $start);
            foreach ($topics as $topic) {
                $matches = [];
                if (preg_match('#\#(' . implode('|', array_keys($feedback_type_codes)) . ')_(.*)$#', $topic['t_description'], $matches) != 0) {
                    $feedback_type_code = $matches[1];
                    $content_id = $matches[2];

                    $timestamp = $topic['t_cache_first_time'];
                    $timestamp = tz_time($timestamp, $server_timezone);

                    $month = to_epoch_interval_index($timestamp, 'months');

                    $num_comments = $topic['t_cache_num_posts'] - 1;

                    foreach (array_keys($date_pivots) as $pivot) {
                        $pivot_value = $this->calculate_date_pivot_value($pivot, $timestamp);

                        if (!isset($data_buckets['comments'][$month][$pivot][$pivot_value][$feedback_type_code])) {
                            $data_buckets['comments'][$month][$pivot][$pivot_value][$feedback_type_code] = [0, 0];
                        }
                        $data_buckets['comments'][$month][$pivot][$pivot_value][$feedback_type_code][0] += $num_comments;
                        $data_buckets['comments'][$month][$pivot][$pivot_value][$feedback_type_code][1]++;

                        $comment_bracket = $this->find_value_bracket($this->comments_brackets, $num_comments);
                        if ($comment_bracket !== null) {
                            if (!isset($data_buckets['comments_tallies'][$month][''][$comment_bracket])) {
                                $data_buckets['comments_tallies'][$month][''][$comment_bracket] = 0;
                            }
                            $data_buckets['comments_tallies'][$month][''][$comment_bracket]++;
                        }
                    }
                }
            }

            $start += $max;
        } while (!empty($topics));
    }

    /**
     * Generate final data from preprocessed data.
     *
     * @param  string $bucket Data bucket we want data for
     * @param  string $pivot Pivot value
     * @param  array $filters Map of filters (including pivot if applicable)
     * @return ?array Final data in standardised map format (null: could not generate)
     */
    public function generate_final_data(string $bucket, string $pivot, array $filters) : ?array
    {
        switch ($bucket) {
            case 'comments':
                $range = $this->convert_month_range_filter_to_pair($filters[$bucket . '__month_range']);

                $data = $this->fill_data_by_date_pivots($pivot, $range[0], $range[1]);

                $where = [
                    'p_bucket' => $bucket,
                    'p_pivot' => $pivot,
                ];
                $extra = '';
                $extra .= ' AND p_month>=' . strval($range[0]);
                $extra .= ' AND p_month<=' . strval($range[1]);
                $data_rows = $GLOBALS['SITE_DB']->query_select('stats_preprocessed', ['p_data'], $where, $extra);
                foreach ($data_rows as $data_row) {
                    $_data = @unserialize($data_row['p_data']);
                    foreach ($_data as $pivot_value => $_) {
                        $pivot_value = $this->make_date_pivot_value_nice($pivot, $pivot_value);

                        $total_posts = 0;
                        $total_topics = 0;

                        foreach ($_ as $feedback_type_code => $value) {
                            if ((!empty($filters[$bucket . '__content_type'])) && ($filters[$bucket . '__content_type'] != $feedback_type_code)) {
                                continue;
                            }

                            $total_posts += $value[0];
                            $total_topics += $value[1];
                        }

                        $data[$pivot_value] += floatval($total_posts) / floatval($total_topics);
                    }
                }

                if (array_sum($data) == 0) {
                    $data = [];
                }

                return [
                    'type' => null,
                    'data' => $data,
                    'x_axis_label' => do_lang_tempcode('TIME_IN_TIMEZONE', escape_html(make_nice_timezone_name(get_site_timezone()))),
                    'y_axis_label' => do_lang_tempcode('COUNT_NEW'),
                ];

            case 'comments_tallies':
                $range = $this->convert_month_range_filter_to_pair($filters[$bucket . '__month_range']);

                $data = [];
                foreach ($this->comments_brackets as $bracket) {
                    $data[$bracket] = 0;
                }

                $where = [
                    'p_bucket' => $bucket,
                    'p_pivot' => $pivot,
                ];
                $extra = '';
                $extra .= ' AND p_month>=' . strval($range[0]);
                $extra .= ' AND p_month<=' . strval($range[1]);
                $data_rows = $GLOBALS['SITE_DB']->query_select('stats_preprocessed', ['p_data'], $where, $extra);
                foreach ($data_rows as $data_row) {
                    $_data = @unserialize($data_row['p_data']);
                    foreach ($_data as $bracket => $total) {
                        $data[$bracket] += $total;
                    }
                }

                if (array_sum($data) == 0) {
                    $data = [];
                }

                return [
                    'type' => self::GRAPH_BAR_CHART,
                    'data' => $data,
                    'x_axis_label' => do_lang_tempcode('COMMENTS'),
                    'y_axis_label' => do_lang_tempcode('ENTRIES'),
                ];
        }

        fatal_exit(do_lang_tempcode('INTERNAL_ERROR', escape_html('94469dd17f8452eea3bb965c3b99bb51')));
    }
}
