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

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__notification_poller()
{
    if (!defined('NOTIFICATION_POLL_FREQUENCY')) {
        define('NOTIFICATION_POLL_FREQUENCY', intval(get_option('notification_poll_frequency')));

        define('NOTIFICATION_POLL_SAFETY_LAG_SECS', 8); // Assume a request could have taken this long to happen, so we look back a little further even than NOTIFICATION_POLL_FREQUENCY
    }
}

/**
 * Notification entry script.
 */
function notification_script()
{
    $type = get_param_string('type');
    switch ($type) {
        case 'mark_all_read':
            notification_mark_all_read_script();
            // no break (we also want to call poller)
        case 'poller':
            notification_poller_script();
            break;
        case 'display':
            notification_display_script();
            break;
    }
}

/**
 * Notification entry script: mark all read.
 */
function notification_mark_all_read_script()
{
    $GLOBALS['SITE_DB']->query_update('digestives_tin', ['d_read' => 1], ['d_read' => 0, 'd_to_member_id' => get_member()]);

    delete_cache_entry('_get_notifications', null, get_member());
}

/**
 * Notification entry script: display notifications.
 */
function notification_display_script()
{
    prepare_backend_response('text/plain');

    $max = post_param_integer('max', null);

    list($tpl, ,) = get_web_notifications($max);
    $tpl->evaluate_echo();
}

/**
 * Notification entry script: poll for new notifications.
 */
function notification_poller_script()
{
    $xml = '';

    require_code('xml');

    prepare_backend_response();

    $xml .= '<' . '?xml version="1.0" encoding="' . escape_html(get_charset()) . '" ?' . '>
' . get_xml_entities() . '
<response>
    <result>
        <time>' . strval(time()) . '</time>
    ';

    $time_barrier = get_param_integer('time_barrier', time() - NOTIFICATION_POLL_FREQUENCY - NOTIFICATION_POLL_SAFETY_LAG_SECS);

    $max = get_param_integer('max', 10);

    $forced_update = (get_param_integer('forced_update', 0) == 1);

    // Notifications

    if (is_guest()) {
        $rows = [];
    } else {
        $query = 'SELECT * FROM ' . get_table_prefix() . 'digestives_tin WHERE d_to_member_id=' . strval(get_member());
        $query .= ' AND d_date_and_time>=' . strval($time_barrier);
        $query .= ' AND d_read=0';
        $query .= ' AND d_frequency=' . strval(A_WEB_NOTIFICATION);
        $rows = $GLOBALS['SITE_DB']->query($query, $max);
    }

    if ((!empty($rows)) || ($forced_update)) {
        foreach ($rows as $row) {
            $xml .= web_notification_to_xml($row);
        }

        if ($max !== null) {
            list($display, , $unread) = get_web_notifications($max, 0, $forced_update);
            $xml .= '
                    <display_web_notifications>' . $display->evaluate() . '</display_web_notifications>
                    <unread_web_notifications>' . strval($unread) . '</unread_web_notifications>
            ';
        }

        // Only keep around for X days
        $sql = 'd_frequency=' . strval(A_WEB_NOTIFICATION) . ' AND d_date_and_time<' . strval(time() - 60 * 60 * 24 * intval(get_option('notification_keep_days')));
        $start = 0;
        $max = 100;
        $found_something = false;
        do {
            $rows = $GLOBALS['SITE_DB']->query('SELECT d_message FROM ' . get_table_prefix() . 'digestives_tin WHERE ' . $sql, $max, $start);
            foreach ($rows as $row) {
                $found_something = true;
                delete_lang($row['d_message']);
            }

            $start += $max;
        } while (!empty($rows));
        if ($found_something) {
            $GLOBALS['SITE_DB']->query('DELETE FROM ' . get_table_prefix() . 'digestives_tin WHERE ' . $sql);
            delete_cache_entry('_get_notifications', null, get_member());
        }
    }

    // Private topics

    if (get_forum_type() == 'cns') {
        if (get_option('pt_notifications_as_web') == '0') {
            require_code('cns_notifications');
            $rows = cns_get_pp_rows(null, true, false, $time_barrier);

            if ((!empty($rows)) || ($forced_update)) {
                foreach ($rows as $row) {
                    $xml .= pt_to_xml($row);
                }

                if ($max !== null) {
                    list($display, $unread) = get_pts($max);
                    $xml .= '
                        <display_pts>' . $display->evaluate() . '</display_pts>
                        <unread_pts>' . strval($unread) . '</unread_pts>
                    ';
                }
            }
        }
    }

    $xml .= '
    </result>
</response>
';
    echo $xml;
}

/**
 * Get web notification templating.
 *
 * @param  ?integer $max Number of notifications to show (null: no limit)
 * @param  integer $start Start offset
 * @param  boolean $skip_cache Whether to skip serving results from the cache
 * @return array A tuple: Templating, Max rows, unread rows
 */
function get_web_notifications(?int $max = null, int $start = 0, bool $skip_cache = false) : array
{
    if (is_guest()) {
        return [new Tempcode(), 0, 0];
    }

    if (($start == 0) && !$skip_cache) {
        $test = get_cache_entry('_get_notifications', serialize([$max]), CACHE_AGAINST_MEMBER, 10000);
        if ($test !== null) {
            return $test;
        }
    }

    $where = [
        'd_to_member_id' => get_member(),
        'd_frequency' => A_WEB_NOTIFICATION,
    ];

    $rows = $GLOBALS['SITE_DB']->query_select('digestives_tin', ['*'], $where, 'ORDER BY d_date_and_time DESC', $max, $start);
    $out = new Tempcode();
    foreach ($rows as $row) {
        $member_id = $row['d_from_member_id'];
        if ($member_id <= 0) {
            $username = do_lang('SYSTEM');
            $from_url = '';
            $avatar_url = find_theme_image('cns_default_avatars/default');
        } else {
            $username = $GLOBALS['FORUM_DRIVER']->get_username($member_id, true);
            $from_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($member_id, true);
            $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($member_id);
        }

        $_message = get_translated_tempcode('digestives_tin', $row, 'd_message');

        $url = null;
        switch ($row['d_notification_code']) {
            case 'cns_topic':
                if (is_numeric($row['d_code_category'])) { // Straight forward topic notification
                    $url = $GLOBALS['FORUM_DRIVER']->topic_url(intval($row['d_code_category']), '', true);
                }
                break;
        }

        $rendered = do_template('NOTIFICATION_WEB', [
            '_GUID' => '314db5380aecd610c7ad2a013743f614',
            'ID' => strval($row['id']),
            'SUBJECT' => $row['d_subject'],
            'MESSAGE' => $_message,
            'FROM_USERNAME' => $username,
            'FROM_MEMBER_ID' => strval($member_id),
            'URL' => $url,
            'FROM_URL' => $from_url,
            'FROM_AVATAR_URL' => $avatar_url,
            'PRIORITY' => strval($row['d_priority']),
            '_TIME' => strval($row['d_date_and_time']),
            'DATE' => get_timezoned_date_time($row['d_date_and_time']),
            'NOTIFICATION_CODE' => $row['d_notification_code'],
            'CODE_CATEGORY' => $row['d_code_category'],
            'HAS_READ' => ($row['d_read'] == 1),
        ]);
        $out->attach($rendered);
    }

    $max_rows = $GLOBALS['SITE_DB']->query_select_value('digestives_tin', 'COUNT(*)', $where);
    $unread_rows = $GLOBALS['SITE_DB']->query_select_value('digestives_tin', 'COUNT(*)', $where + ['d_read' => 0]);

    $ret = [$out, $max_rows, $unread_rows];

    if ($start == 0) {
        require_code('caches2');
        set_cache_entry('_get_notifications', 60 * 24, serialize([$max]), $ret, CACHE_AGAINST_MEMBER);
    }

    return $ret;
}

/**
 * Get XML for sending a notification to the current user's web browser.
 *
 * @param  array $row Notification row
 * @return string The XML
 */
function web_notification_to_xml(array $row) : string
{
    $member_id = $row['d_from_member_id'];
    $username = $GLOBALS['FORUM_DRIVER']->get_username($member_id, true);
    $from_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($member_id, true);
    $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($member_id);

    $_message = get_translated_tempcode('digestives_tin', $row, 'd_message');

    $rendered = do_template('NOTIFICATION_WEB_DESKTOP', [
        '_GUID' => '1641fa5c5b62421ae535680859e89636',
        'ID' => strval($row['id']),
        'SUBJECT' => $row['d_subject'],
        'MESSAGE' => $_message,
        'FROM_USERNAME' => $username,
        'FROM_MEMBER_ID' => strval($member_id),
        'FROM_URL' => $from_url,
        'FROM_AVATAR_URL' => $avatar_url,
        'PRIORITY' => strval($row['d_priority']),
        '_TIME' => strval($row['d_date_and_time']),
        'DATE' => get_timezoned_date_time($row['d_date_and_time']),
        'NOTIFICATION_CODE' => $row['d_notification_code'],
        'CODE_CATEGORY' => $row['d_code_category'],
    ]);

    $sound = ($GLOBALS['FORUM_DRIVER']->get_member_row_field($member_id, 'm_sound_enabled') == 1);

    return '
        <web_notification
            id="' . strval($row['id']) . '"
            subject="' . escape_html($row['d_subject']) . '"
            rendered="' . escape_html($rendered->evaluate()) . '"
            message="' . escape_html(static_evaluate_tempcode($_message)) . '"
            from_username="' . escape_html($username) . '"
            from_member_id="' . escape_html(strval($member_id)) . '"
            from_url="' . escape_html(is_object($from_url) ? $from_url->evaluate() : $from_url) . '"
            from_avatar_url="' . escape_html($avatar_url) . '"
            priority="' . escape_html(strval($row['d_priority'])) . '"
            date_timestamp="' . escape_html(strval($row['d_date_and_time'])) . '"
            date_written_time="' . escape_html(get_timezoned_date_time($row['d_date_and_time'])) . '"
            notification_code="' . escape_html($row['d_notification_code']) . '"
            code_category="' . escape_html($row['d_code_category']) . '"
            sound="' . ($sound ? 'on' : 'off') . '"
        />
    ';
}

/**
 * Get PTs templating.
 *
 * @param  ?integer $max Number of PTs to show (null: no limit)
 * @param  integer $start Start offset
 * @return array A pair: Templating, Max rows
 */
function get_pts(?int $max = null, int $start = 0) : array
{
    if (get_forum_type() != 'cns') {
        return [new Tempcode(), 0];
    }

    if (is_guest()) {
        return [new Tempcode(), 0];
    }

    if (!addon_installed('cns_forum')) {
        return [new Tempcode(), 0];
    }

    if ($start == 0) {
        $test = get_cache_entry('_get_pts', serialize([$max]), CACHE_AGAINST_MEMBER, 10000);
        if ($test !== null) {
            return $test;
        }
    }

    cns_require_all_forum_stuff();

    require_code('cns_notifications');
    $rows = cns_get_pp_rows($max, false, false);
    $max_rows = count(cns_get_pp_rows(intval(get_option('general_safety_listing_limit')), true, false));

    $out = new Tempcode();
    foreach ($rows as $i => $topic) {
        $topic_url = build_url(['page' => 'topicview', 'id' => $topic['t_id']], get_module_zone('topicview'));
        $title = $topic['t_cache_first_title'];
        $date = get_timezoned_date_time($topic['t_cache_last_time']);
        $num_posts = $topic['t_cache_num_posts'];

        $last_post_by_username = $topic['t_cache_last_username'];
        $last_post_by_member_url = $GLOBALS['CNS_DRIVER']->member_profile_url($topic['t_cache_last_member_id'], true);

        $with_poster_id = ($topic['t_pt_from_member'] == get_member()) ? $topic['t_pt_to_member'] : $topic['t_pt_from_member'];
        $with_username = $GLOBALS['FORUM_DRIVER']->get_username($with_poster_id, false, USERNAME_DEFAULT_NULL);
        if ($with_username === null) {
            $with_username = do_lang('UNKNOWN');
        }
        $with_member_url = $GLOBALS['CNS_DRIVER']->member_profile_url($with_poster_id, true);

        $by_poster_id = $topic['t_pt_from_member'];
        $by_username = $GLOBALS['FORUM_DRIVER']->get_username($by_poster_id, false, USERNAME_DEFAULT_NULL);
        if ($by_username === null) {
            $by_username = do_lang('UNKNOWN');
        }
        $by_member_url = $GLOBALS['CNS_DRIVER']->member_profile_url($by_poster_id, true);

        $to_poster_id = $topic['t_pt_to_member'];
        $to_username = $GLOBALS['FORUM_DRIVER']->get_username($to_poster_id, false, USERNAME_DEFAULT_NULL);
        if ($to_username === null) {
            $to_username = do_lang('UNKNOWN');
        }
        $to_member_url = $GLOBALS['CNS_DRIVER']->member_profile_url($to_poster_id, true);

        $is_unread = ($topic['t_cache_last_time'] > time() - 60 * 60 * 24 * intval(get_option('post_read_history_days'))) && (($topic['l_time'] === null) || ($topic['l_time'] < $topic['t_cache_last_time']));

        $out->attach(do_template('CNS_PRIVATE_TOPIC_LINK', [
            '_GUID' => '6a36e785b05d10f53e7ee76acdfb9f80',
            'TOPIC_URL' => $topic_url,
            'TITLE' => $title,
            'DATE' => $date,
            'DATE_RAW' => strval($topic['t_cache_last_time']),
            'LAST_POST_BY_POSTER_URL' => $last_post_by_member_url,
            'LAST_POST_BY_USERNAME' => $last_post_by_username,
            'LAST_POST_BY_POSTER_ID' => strval($topic['t_cache_last_member_id']),
            'WITH_POSTER_URL' => $with_member_url,
            'WITH_USERNAME' => $with_username,
            'WITH_POSTER_ID' => strval($with_poster_id),
            'BY_POSTER_URL' => $by_member_url,
            'BY_USERNAME' => $by_username,
            'BY_POSTER_ID' => strval($by_poster_id),
            'TO_POSTER_URL' => $to_member_url,
            'TO_USERNAME' => $to_username,
            'TO_POSTER_ID' => strval($to_poster_id),
            '_NUM_POSTS' => strval($num_posts),
            'NUM_POSTS' => integer_format($num_posts, 0),
            'HAS_READ' => !$is_unread,
        ]));

        if ($i === $max) {
            break;
        }
    }

    $ret = [$out, $max_rows];

    if ($start == 0) {
        require_code('caches2');
        set_cache_entry('_get_pts', 60 * 24, serialize([$max]), $ret, CACHE_AGAINST_MEMBER);
    }

    return $ret;
}

/**
 * Get XML for sending a PT alert to the current user's web browser.
 *
 * @param  array $row Notification row
 * @return string The XML
 */
function pt_to_xml(array $row) : string
{
    $member_id = $row['p_posting_member'];
    $username = $GLOBALS['FORUM_DRIVER']->get_username($member_id);
    $url = $GLOBALS['FORUM_DRIVER']->member_profile_url($member_id, true);
    $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($member_id);

    $just_post_row = db_map_restrict($row, ['id', 'p_post'], ['id' => 'p_id']);
    $_message = get_translated_tempcode('f_posts', $just_post_row, 'p_post', $GLOBALS['FORUM_DB']);

    $rendered = do_template('NOTIFICATION_PT_DESKTOP', [
        '_GUID' => '624df70cf0cbb796c5d5ce1d18ae39f7',
        'ID' => strval($row['p_id']),
        'SUBJECT' => $row['t_cache_first_title'],
        'MESSAGE' => $_message,
        'FROM_USERNAME' => $username,
        'FROM_MEMBER_ID' => strval($member_id),
        'URL' => $url,
        'FROM_AVATAR_URL' => $avatar_url,
        '_TIME' => strval($row['p_time']),
        'DATE' => get_timezoned_date_time($row['p_time']),
    ]);

    return '
        <pt
            id="' . strval($row['p_id']) . '"
            subject="' . escape_html($row['t_cache_first_title']) . '"
            rendered="' . escape_html($rendered->evaluate()) . '"
            message="' . escape_html(static_evaluate_tempcode($_message)) . '"
            from_username="' . escape_html($username) . '"
            from_member_id="' . escape_html(strval($member_id)) . '"
            url="' . escape_html(is_object($url) ? $url->evaluate() : $url) . '"
            from_avatar_url="' . escape_html($avatar_url) . '"
            date_timestamp="' . escape_html(strval($row['p_time'])) . '"
            date_written_time="' . escape_html(get_timezoned_date_time($row['p_time'])) . '"
            sound="on"
        />
    ';
}
