<?php
/*
 *   $Id$
 *
 *   AbanteCart, Ideal OpenSource Ecommerce Solution
 *   http://www.AbanteCart.com
 *
 *   Copyright © 2011-2025 Belavier Commerce LLC
 *
 *   This source file is subject to Open Software License (OSL 3.0)
 *   License details are bundled with this package in the file LICENSE.txt.
 *   It is also available at this URL:
 *   <http://www.opensource.org/licenses/OSL-3.0>
 *
 *  UPGRADE NOTE:
 *    Do not edit or add to this file if you wish to upgrade AbanteCart to newer
 *    versions in the future. If you wish to customize AbanteCart for your
 *    needs, please refer to http://www.AbanteCart.com for more information.
 */
if (!defined('DIR_CORE')) {
    header('Location: static_pages/');
}

/**
 * Message service for storing and grouping system notices, warnings, and errors.
 */
class AMessage
{
    /** @var ADB Database connection */
    protected $db;
    /** @var ASession Current session */
    protected $session;
    /** @var AHtml HTML helper */
    protected $html;
    /** @var Registry Service locator/registry */
    protected $registry;

    public function __construct()
    {
        $this->registry = Registry::getInstance();
        $this->db = $this->registry->get('db');
        $this->html = $this->registry->get('html');
        $this->session = $this->registry->get('session');
    }

    /**
     * Save a notice message.
     *
     * @param string $title Message title.
     * @param string $message Message body.
     * @param bool $repetition_group Group repetitions by the same title.
     *
     * @return void
     * @throws AException
     */
    public function saveNotice($title, $message, $repetition_group = true)
    {
        $this->_saveMessage($title, $message, 'N', $repetition_group);
    }

    /**
     * Save a warning message.
     *
     * @param string $title Message title.
     * @param string $message Message body.
     * @param bool $repetition_group Group repetitions by the same title.
     *
     * @return void
     * @throws AException
     */
    public function saveWarning($title, $message, $repetition_group = true)
    {
        $this->_saveMessage($title, $message, 'W', $repetition_group);
    }

    /**
     * Save an error message.
     *
     * @param string $title Message title.
     * @param string $message Message body.
     * @param bool $repetition_group Group repetitions by the same title.
     *
     * @return void
     * @throws AException
     */
    public function saveError($title, $message, $repetition_group = true)
    {
        $this->_saveMessage($title, $message, 'E', $repetition_group);
    }

    /**
     * Save a message with a given status.
     *
     * @param string $title Message title.
     * @param string $message Message body.
     * @param string $status Message status: 'N' (notice), 'W' (warning), 'E' (error).
     * @param bool $repetition_group Group repetitions by the same title.
     *
     * @return int Message ID.
     * @throws AException
     */
    protected function _saveMessage($title, $message, $status, $repetition_group = true)
    {
        $last_message = $this->getLikeMessage($title);
        // if the last message is equal new - update, it's repeated.
        // update counter and update the message body as the last one can be different
        if ($last_message['title'] == $title && $repetition_group) {
            $this->db->query(
                "UPDATE " . $this->db->table("messages") . " 
                SET `repeated` = `repeated` + 1, 
                    `viewed`='0', 
                    `message` = '" . $this->db->escape($message) . "'
                WHERE msg_id = '" . (int)$last_message['msg_id'] . "'"
            );
            $msg_id = (int)$last_message['msg_id'];
        } else {
            $this->db->query(
                "INSERT INTO " . $this->db->table("messages") . " 
                SET `title` = '" . $this->db->escape($title) . "',
                    `message` = '" . $this->db->escape($message) . "',
                    `status` = '" . $this->db->escape($status) . "'"
            );
            $msg_id = (int)$this->db->getLastId();
        }
        return $msg_id;
    }

    /**
     * @param int $msg_id
     * @throws AException
     */
    public function deleteMessage($msg_id)
    {
        $this->db->query(
            "DELETE FROM " . $this->db->table("messages") . " 
            WHERE `msg_id` = " . (int)$msg_id
        );
        return true;
    }

    /**
     * @param int $msg_id
     *
     * @return array
     * @throws AException
     */
    public function getMessage($msg_id)
    {
        $this->markAsRead($msg_id);
        $query = $this->db->query(
            "SELECT * 
            FROM " . $this->db->table("messages") . " 
            WHERE msg_id = " . (int)$msg_id
        );
        $row = $query->row;
        if ($row) {
            // replace html-links in message
            $row['message'] = $this->html->convertLinks($row['message'], 'message');
        }
        return $row;
    }

    /**
     * @param int $start
     * @param int $limit
     * @param string $sort
     * @param string $order
     *
     * @return array
     * @throws AException
     */
    public function getMessages($start = 0, $limit = 0, $sort = '', $order = 'DESC')
    {
        $sort = !$sort ? 'viewed' : $this->db->escape($sort);
        $limit_str = '';
        if ($limit > 0) {
            $limit_str = "LIMIT " . (int)$start . ", " . (int)$limit;
        }
        $sql = "SELECT " . $this->db->getSqlCalcTotalRows() . " * 
                FROM " . $this->db->table("messages") . " 
                ORDER BY " . $sort . " " . $order . ", date_modified DESC, msg_id 
                DESC " . $limit_str;
        $query = $this->db->query($sql);
        $output = $query->rows;
        if ($output) {
            $output[0]['total_num_rows'] = $this->db->getTotalNumRows();
        }
        return $output;
    }

    /**
     * @param string $title
     *
     * @return array
     * @throws AException
     */
    public function getLikeMessage($title)
    {
        $query = $this->db->query(
            "SELECT * 
            FROM " . $this->db->table("messages") . " 
            WHERE title='" . $this->db->escape($title) . "'"
        );
        return $query->row;
    }

    /**
     * @return int
     * @throws AException
     */
    public function getTotalMessages()
    {
        $query = $this->db->query(
            "SELECT COUNT(*) AS total 
            FROM " . $this->db->table("messages") . " "
        );
        return (int)$query->row['total'];
    }

    /**
     * @param int $msg_id
     *
     * @return bool
     * @throws AException
     */
    public function markAsRead($msg_id)
    {
        $this->db->query(
            "UPDATE " . $this->db->table("messages") . " 
            SET viewed = viewed + 1, date_modified = date_modified   
            WHERE `msg_id` = '" . (int)$msg_id . "'"
        );
        return true;
    }

    /**
     * @param int $msg_id
     *
     * @return bool
     * @throws AException
     */
    public function markAsUnRead($msg_id)
    {
        $msg_info = $this->getMessage($msg_id);
        if ($msg_info['viewed']) {
            $this->db->query(
                "UPDATE " . $this->db->table("messages") . " 
                SET viewed = 0, date_modified = date_modified   
                WHERE `msg_id` = '" . (int)$msg_id . "'"
            );
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param string $title
     * @param string $message
     * @param string $status
     */
    public function saveForDisplay($title, $message, $status)
    {
        $this->session->data['ac_messages'][] = [$title, $message, $status];
    }

    /**
     * @return array
     */
    public function getForDisplay()
    {
        $messages = [];
        if ($this->session->data['ac_messages']) {
            $messages = $this->session->data['ac_messages'];
            unset($this->session->data['ac_messages']);
        }
        return $messages;
    }

    /**
     * @param array $no_delete
     *
     * @return bool
     * @throws AException
     */
    public function purgeANTMessages($no_delete = [])
    {
        if (!$no_delete || !is_array($no_delete)) {
            return false;
        }
        $ids = [];
        foreach ($no_delete as $msg_id) {
            $ids[] = $this->db->escape($msg_id);
        }

        $sql = "DELETE FROM " . $this->db->table("ant_messages");
        if ($ids) {
            $sql .= " WHERE id NOT IN ('" . implode("', '", $ids) . "')";
        }
        $this->db->query($sql);
        return true;
    }

    /**
     * @param array $data
     *
     * @return null
     * @throws AException
     */
    public function saveANTMessage($data = [])
    {
        if (!$data || !$data['message_id']) {
            return false;
        }

        // need to find the message with the same ID and language. If the language is not set - find for all,
        // if language_code is empty, it means that the banner shows for all interface languages
        $sql = "SELECT *
                 FROM " . $this->db->table("ant_messages") . " 
                 WHERE id = '" . $this->db->escape($data['message_id']) . "'
                 " . (
            $data['language_code']
                ? "AND language_code = '" . $this->db->escape($data['language_code']) . "'"
                : ""
            )
            . " ORDER BY viewed_date ASC";
        $result = $this->db->query($sql);

        $exists = [];
        $viewed = 0;
        $last_view = null;
        if ($result->num_rows) {
            foreach ($result->rows as $row) {
                $exists[] = "'" . $row['id'] . "'";
                $viewed += $row['viewed'];
                $last_view = $row['viewed_date'];
            }
            $this->db->query(
                "DELETE FROM " . $this->db->table("ant_messages") . " 
                WHERE id IN (" . implode(",", $exists) . ")"
            );
        }
        $data['end_date'] = !$data['end_date'] || $data['end_date'] == '0000-00-00 00:00:00'
            ? '2030-01-01'
            : $data['end_date'];
        $data['priority'] = !(int)$data['priority'] ? 1 : (int)$data['priority'];
        $sql = "INSERT INTO " . $this->db->table("ant_messages") . " 
                (`id`,
                `priority`,
                `placeholder`,
                `start_date`,
                `end_date`,
                `viewed_date`,
                `viewed`,
                `title`,
                `description`,
                `html`,
                `url`,
                `language_code`)
                VALUES ('" . $this->db->escape($data['message_id']) . "',
                        '" . $this->db->escape($data['priority']) . "',
                        '" . $this->db->escape($data['placeholder']) . "',
                        '" . $this->db->escape($data['start_date']) . "',
                        '" . $this->db->escape($data['end_date']) . "',
                        '" . $this->db->escape($last_view) . "',
                        '" . (int)$viewed . "',
                        '" . $this->db->escape($data['title']) . "',
                        '" . $this->db->escape($data['description']) . "',
                        '" . $this->db->escape($data['html']) . "',
                        '" . $this->db->escape($data['url']) . "',
                        '" . $this->db->escape($data['language_code']) . "')";
        $this->db->query($sql, true);
        return true;
    }

    /**
     * @return array
     * @throws AException
     */
    public function getANTMessage(?bool $notViewed = true)
    {
        // delete expired first
        $this->db->query("DELETE FROM " . $this->db->table("ant_messages") . " WHERE end_date < NOW()");
        $sql = "SELECT *
                 FROM " . $this->db->table("ant_messages") . " 
                 WHERE start_date < NOW() and end_date > NOW()
                 " . ($notViewed ? ' AND viewed < 1' : '') . " 
                    AND ( language_code = '" . $this->registry->get('config')->get('admin_language') . "' 
                        OR COALESCE(language_code,'*') = '*') ";
        if ($notViewed) {
            $sql .= "ORDER BY viewed ASC, priority DESC, COALESCE(language_code,'') DESC, COALESCE(url,'') DESC ";
        } else {
            $sql .= "ORDER BY RAND() ";
        }
        $sql .= " LIMIT 1";
        $result = $this->db->query($sql);
        if ($result->num_rows) {
            $output = $result->row['html'] ?: $result->row['description'];
            return [
                'id'     => $result->row['id'] ?? null,
                'viewed' => $result->row['viewed'] ?? 0,
                'html'   => $output ?? '',
            ];
        }
        return [];
    }

    /**
     * @param string|array $placeholder
     * @param string $messageId
     * @param bool $unread
     * @return array
     * @throws AException
     */
    public function getANTMessageByPlaceholder(string|array $placeholder, string $messageId = '', bool $unread = false)
    {
        if (is_string($placeholder)) {
            $placeholder = ['include' => [$placeholder]];
        }
        foreach (['include', 'exclude'] as $key) {
            $placeholder[$key] ??= [];
            foreach ($placeholder[$key] as &$pl) {
                $pl = $this->db->escape($pl);
            }
        }

        $sql = "SELECT *
                 FROM " . $this->db->table("ant_messages") . " 
                 WHERE ( language_code = '" . $this->registry->get('config')->get('admin_language') . "' 
                        OR COALESCE(language_code,'*') = '*') 
                 " . ($placeholder['include'] ? " AND placeholder IN ('" . implode("', '", $placeholder['include']) . "') " : '')
            . ($placeholder['exclude'] ? " AND placeholder NOT IN ('" . implode("', '", $placeholder['exclude']) . "') " : '')
            . ($unread ? ' AND viewed < 1 ' : '');
        if (!$messageId) {
            $sql .= "ORDER BY viewed ASC, priority DESC, COALESCE(language_code,'') DESC, COALESCE(url,'') DESC ";
        } else {
            $sql .= " AND id LIKE '" . $this->db->escape($messageId) . "' ";
        }
        $sql .= " LIMIT 1";
        $result = $this->db->query($sql);
        if ($result->num_rows) {
            $output = $result->row['html'] ?: $result->row['description'];
            return [
                'id'     => $result->row['id'] ?? null,
                'viewed' => $result->row['viewed'] ?? 0,
                'html'   => $this->html->convertLinks($output ?? ''),
            ];
        }
        return [];
    }

    /**
     * @param string $message_id
     * @param string $language_code
     *
     * @return string
     * @throws AException
     */
    public function markViewedANT($message_id, $language_code)
    {
        if (!has_value($message_id) || !has_value($language_code)) {
            return null;
        }
        $sql = "UPDATE  " . $this->db->table("ant_messages") . " 
                SET viewed = viewed+1 , viewed_date = NOW(), date_modified = date_modified 
                WHERE id = '" . $this->db->escape($message_id) . "'
                    AND language_code = '" . $this->db->escape($language_code) . "'";
        $this->db->query($sql);
        return $message_id;
    }

    /**
     * @return array
     * @throws AException
     */
    public function getShortList()
    {
        $output = [];
        $result = $this->db->query(
            "SELECT UPPER(status) as status, COUNT(msg_id) as count
            FROM " . $this->db->table("messages") . " 
            WHERE viewed<'1'
            GROUP BY status"
        );
        $total = 0;
        foreach ($result->rows as $row) {
            $output['count'][$row['status']] = ( int )$row['count'];
            $total += ( int )$row['count'];
        }

        $output['total'] = $total;
        //get last 9 messages
        $result = $this->db->query(
            "(SELECT msg_id, title, message, status, viewed, date_modified
            FROM " . $this->db->table('messages') . "
            WHERE viewed<'1'
            ORDER BY date_modified DESC
            LIMIT 0,9)"
        );
        $output['shortlist'] = $result->rows;
        return $output;
    }
}