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

use Symfony\Component\Mailer\Exception\TransportExceptionInterface;

if (!defined('DIR_CORE')) {
    header('Location: static_pages/');
}

/**
 * Class AIMManager
 *
 * @property Registry $registry
 */
class AIMManager extends AIM
{

    public $errors = [];
    //add sendpoints list for admin-side
    /**
     * @var array
     * * NOTE:
     * each key of an array is text_id of sendpoint.
     * To get sendpoint title needs to request language definition in format im_sendpoint_name_{sendpoint_text_id}
     * All core/default sendpoint 'text_key' saved in common/im language block for both sides (admin & storefront)
     * Values of an array is language definitions key that stores in the same block.
     * These values can have %s that will be replaced by sendpoint text variables.
     * for example, the message has url to the product page.
     * Text will have #storefront#rt=product/product&product_id=%s
     * and the customer will receive the full url to the product.
     * Some sendpoints have few text variables, for ex. order status and order status name
     * For additional sendpoints ( from extensions) you can store language keys wherever you want.
     *
     */
    public $admin_sendpoints = [
        'order_update'              => [
            0 => ['text_key' => 'im_order_update_text_to_customer'],
            1 => [],
        ],
        'account_update'            => [
            0 => [],
            1 => ['text_key' => 'im_account_update_text_to_admin', 'force_send' => ['email']],
        ],
        'customer_account_approved' => [
            0 => ['text_key' => 'im_customer_account_approved_text_to_customer', 'force_send' => ['email']],
            1 => [],
        ],
        'customer_account_update'   => [
            0 => ['text_key' => 'im_customer_account_update_text_to_customer', 'force_send' => ['email']],
            1 => [],
        ],
        /* TODO: Need to decide how to handle system messages in respect to IM
        'system_messages'   => [
                0 => [],
                1 => array('text_key' => 'im_system_messages_text_to_admin'),
        ]
        */
    ];

    //NOTE: This class is loaded in INIT for admin only
    public function __construct()
    {
        parent::__construct();
        if (!IS_ADMIN) { // forbid for non-admin calls
            throw new AException (AC_ERR_LOAD, 'Error: permission denied to access class AIMManager');
        }
    }

    /**
     * @param string $name
     * @param array $data_array
     *
     * @return bool
     * @throws AException
     */
    public function addAdminSendPoint($name, $data_array)
    {
        if ($name && !in_array($name, $this->admin_sendpoints)) {
            $this->admin_sendpoints[$name] = $data_array;
        } else {
            $error = new AError('Admin SendPoint ' . $name . ' cannot be added to the list.');
            $error->toLog();
            return false;
        }
        return true;
    }

    /**
     * @param string $sendpoint
     * @param array $msg_details
     * @param string $templateTextId
     * @param array $templateData
     * @param array $attachments
     *
     * @return bool|null
     * @throws AException|TransportExceptionInterface
     */
    public function send($sendpoint, $msg_details = [], $templateTextId = '', $templateData = [], $attachments = [])
    {
        return parent::send($sendpoint, $msg_details, $templateTextId, $templateData, $attachments);
    }

    /**
     * @param int $customer_id
     * @param string $sendpoint
     * @param array $msg_details - structure:
     *                            array(
     *                              message => 'text',
     *                            );
     *                            notes: If a message is not provided, message text will be takes from languages based on a checkpoint text key.
     * @param string $templateTextId
     * @param array $templateData
     * @param array $attachments
     *
     * @return array|bool
     * @throws AException|TransportExceptionInterface
     */
    public function sendToCustomer(
        $customer_id,
        $sendpoint,
        $msg_details = [],
        $templateTextId = '',
        $templateData = [],
        $attachments = []
    )
    {
        if (!$customer_id) {
            return [];
        }
        $this->load->language('common/im');
        $sendpoints_list = $this->admin_sendpoints;
        $customer_im_settings = $this->getCustomerIMSettings($customer_id);
        $this->registry->set('force_skip_errors', true);

        //check sendpoint
        if (!in_array($sendpoint, array_keys($sendpoints_list))) {
            $error = new AError('IM error: Invalid SendPoint ' . $sendpoint . ' was used in IM class. Nothing sent.');
            $error->toLog();
            return false;
        }
        $sendpoint_data = $sendpoints_list[$sendpoint];

        foreach ($this->protocols as $protocol) {
            $driver = null;

            //check protocol status
            if ($protocol == 'email') {
                //email notifications always enabled
                $protocol_status = 1;
            } elseif ((int)$this->config->get('config_storefront_' . $protocol . '_status')
                || (int)$this->config->get('config_admin_' . $protocol . '_status')
            ) {
                $protocol_status = 1;
            } else {
                $protocol_status = 0;
            }

            if (!$protocol_status) {
                continue;
            }

            if ($protocol == 'email') {
                //see AMailAIM class in im.php
                $driver = new AMailIM();
            } else {
                $driver_txt_id = $this->config->get('config_' . $protocol . '_driver');

                //if a driver didn't set - skip protocol
                if (!$driver_txt_id) {
                    continue;
                }

                if (!$this->config->get($driver_txt_id . '_status')) {
                    $error = new AError(
                        'Cannot send notification. Communication driver ' . $driver_txt_id . ' is disabled!'
                    );
                    $error->toLog()->toMessages();
                    continue;
                }

                //use safe usage
                $driver_file = DIR_EXT . $driver_txt_id . DS . 'core' . DS . 'lib' . DS . $driver_txt_id . '.php';
                if (!is_file($driver_file)) {
                    $error = new AError('Cannot find file ' . $driver_file . ' to send notification.');
                    $error->toLog()->toMessages();
                    continue;
                }
                try {
                    /** @noinspection PhpIncludeInspection */
                    include_once($driver_file);
                    //if class of driver
                    $classname = preg_replace('/[^a-zA-Z]/', '', $driver_txt_id);
                    if (!class_exists($classname)) {
                        $error = new AError('IM-driver ' . $driver_txt_id . ' load error.');
                        $error->toLog()->toMessages();
                        continue;
                    }

                    $driver = new $classname();
                } catch (Exception $e) {
                    $this->log->write(__CLASS__ . '::' . __METHOD__ . ": " . $e->getMessage());
                }
            }
            //if a driver cannot be initialized - skip protocol
            if ($driver === null) {
                continue;
            }

            $store_name = $this->config->get('store_name') . ": ";
            //send notification to customer
            if (!empty($sendpoint_data[0])) {
                //send notification to customer, check if selected or forced
                $force_arr = $sendpoint_data[0]['force_send'];
                $forced = false;
                if (has_value($force_arr) && in_array($protocol, $force_arr)) {
                    $forced = true;
                }
                if ($customer_im_settings[$sendpoint][$protocol] || $forced) {
                    //check is notification for this protocol and sendpoint allowed
                    if ($this->config->get('config_storefront_' . $protocol . '_status') || $protocol == 'email') {
                        $message = $msg_details[0]['message'];
                        if (empty($message)) {
                            //send a default message. Not recommended
                            $message = $this->language->get($sendpoint_data[0]['text_key']);
                        }
                        $message = $this->_process_message_text($message);
                        $to = $this->getCustomerURI($protocol, $customer_id);
                        if ($message && $to) {
                            //use safe call
                            try {
                                if ($protocol == 'email') {
                                    $driver->send(
                                        $to, $store_name . $message, $templateTextId, $templateData, $attachments
                                    );
                                } else {
                                    $driver->send($to, $store_name . $message);
                                }
                            } catch (Exception $e) {
                                $this->log->write($e->getMessage());
                            }
                        }
                    }
                }
            }

            unset($driver);
        }
        $this->registry->set('force_skip_errors', false);
        return true;
    }

    /**
     * @param int $order_id
     * @param array $msg_details - structure:
     *                           array(
     *                           message => 'text',
     *                           );
     *                           notes: If a message is not provided, message text will be takes from languages based on a checkpoint text key.
     * @param string $templateTextId
     * @param array $templateData
     * @param array $attachments
     *
     * @return array|bool
     * @throws AException|TransportExceptionInterface
     */
    public function sendToGuest(
        $order_id,
        $msg_details = [],
        $templateTextId = '',
        $templateData = [],
        $attachments = []
    )
    {
        if (!$order_id) {
            return [];
        }
        $this->load->language('common/im');
        $this->registry->set('force_skip_errors', true);

        foreach ($this->protocols as $protocol) {
            $driver = null;

            //check protocol status
            if ($protocol == 'email') {
                //email notifications always enabled
                $protocol_status = 1;
            } elseif ((int)$this->config->get('config_storefront_' . $protocol . '_status')
                || (int)$this->config->get('config_admin_' . $protocol . '_status')
            ) {
                $protocol_status = 1;
            } else {
                $protocol_status = 0;
            }

            if (!$protocol_status) {
                continue;
            }

            if ($protocol == 'email') {
                //see AMailAIM class in im.php
                $driver = new AMailIM();
            } else {
                $driver_txt_id = $this->config->get('config_' . $protocol . '_driver');

                //if a driver didn't set - skip protocol
                if (!$driver_txt_id) {
                    continue;
                }

                if (!$this->config->get($driver_txt_id . '_status')) {
                    $error = new AError(
                        'Cannot send notification. Communication driver ' . $driver_txt_id . ' is disabled!'
                    );
                    $error->toLog()->toMessages();
                    continue;
                }

                //use safe usage
                $driver_file = DIR_EXT . $driver_txt_id . DS . 'core' . DS . 'lib' . DS . $driver_txt_id . '.php';
                if (!is_file($driver_file)) {
                    $error = new AError('Cannot find file ' . $driver_file . ' to send notification.');
                    $error->toLog()->toMessages();
                    continue;
                }
                try {
                    /** @noinspection PhpIncludeInspection */
                    include_once($driver_file);
                    //if class of driver
                    $classname = preg_replace('/[^a-zA-Z]/', '', $driver_txt_id);
                    if (!class_exists($classname)) {
                        $error = new AError('IM-driver ' . $driver_txt_id . ' load error.');
                        $error->toLog()->toMessages();
                        continue;
                    }

                    $driver = new $classname();
                } catch (Exception $e) {
                    $this->log->write(__CLASS__ . '::' . __METHOD__ . ": " . $e->getMessage());
                }
            }
            //if a driver cannot be initialized - skip protocol
            if ($driver === null) {
                continue;
            }

            $store_name = $this->config->get('store_name') . ": ";
            //check is notification for this protocol and sendpoint allowed
            if ($this->config->get('config_storefront_' . $protocol . '_status') || $protocol == 'email') {
                $message = $msg_details[0]['message'];
                if (empty($message)) {
                    continue;
                }
                $message = $this->_process_message_text($message);
                $to = $this->getCustomerURI($protocol, 0, $order_id);
                if ($message && $to) {
                    //use safe call
                    try {
                        if ($protocol == 'email') {
                            $driver->send($to, $store_name . $message, $templateTextId, $templateData, $attachments);
                        } else {
                            $driver->send($to, $store_name . $message);
                        }
                    } catch (Exception $e) {
                        $this->log->write(__CLASS__ . '::' . __METHOD__ . ": " . $e->getMessage());
                    }
                }
            }
            unset($driver);
        }
        $this->registry->set('force_skip_errors', false);
        return true;
    }

    public function getCustomerIMSettings($customer_id)
    {
        if (!$customer_id) {
            return [];
        }

        //get only active IM drivers
        $im_protocols = $this->getProtocols();
        $im_settings = [];
        $sql = "SELECT *
                FROM " . $this->db->table('customer_notifications') . "
                WHERE customer_id = " . (int)$customer_id;
        $result = $this->db->query($sql);

        foreach ($result->rows as $row) {
            if (!in_array($row['protocol'], $im_protocols)) {
                continue;
            }
            $im_settings[$row['sendpoint']][$row['protocol']] = (int)$row['status'];
        }
        return $im_settings;
    }

    public function getUserIMs($user_id, $store_id)
    {
        $user_id = (int)$user_id;
        $store_id = (int)$store_id;
        if (!$user_id) {
            return [];
        }

        $sql = "SELECT *
                FROM " . $this->db->table('user_notifications') . "
                WHERE user_id=" . $user_id . "
                    AND store_id = '" . $store_id . "'
                ORDER BY `sendpoint`, `protocol`";
        $result = $this->db->query($sql);

        $output = [];
        foreach ($result->rows as $row) {
            $section = (int)$row['section'] ? 'admin' : 'storefront';
            $sendpoint = $row['sendpoint'];
            unset($row['sendpoint']);
            $output[$section][$sendpoint][] = $row;
        }
        return $output;
    }

    /**
     * @param int $user_id
     * @param string $sendpoint
     * @param array $msg_details
     * @param string $templateTextId
     * @param array $templateData
     * @param array $attachments
     *
     * @return null
     *
     * $msg_details structure:
     * array(
     * message => 'text',
     * );
     * notes: If a message is not provided, message text will be takes from languages based on a checkpoint text key.
     * @throws AException|TransportExceptionInterface
     */
    public function sendToUser(
        $user_id,
        $sendpoint,
        $msg_details = [],
        $templateTextId = '',
        $templateData = [],
        $attachments = []
    )
    {
        if (!$user_id) {
            return [];
        }

        $this->load->language('common/im');

        $sendpoints_list = $this->admin_sendpoints;
        $user_im_settings = $this->getUserSendPointSettings($user_id, 'admin', $sendpoint, 0);
        $this->registry->set('force_skip_errors', true);

        //check sendpoint
        if (!in_array($sendpoint, array_keys($sendpoints_list))) {
            $error = new AError('IM error: Invalid SendPoint ' . $sendpoint . ' was used in IM class. Nothing sent.');
            $error->toLog()->toMessages();
            return false;
        }
        $sendpoint_data = $sendpoints_list[$sendpoint];

        foreach ($this->protocols as $protocol) {
            $driver = null;

            //check protocol status
            if ($protocol == 'email') {
                //email notifications always enabled
                $protocol_status = 1;
            } elseif ((int)$this->config->get('config_admin_' . $protocol . '_status')) {
                $protocol_status = 1;
            } else {
                $protocol_status = 0;
            }

            if (!$protocol_status) {
                continue;
            }

            if ($protocol == 'email') {
                //see AMailAIM class in im.php
                $driver = new AMailIM();
            } else {
                $driver_txt_id = $this->config->get('config_' . $protocol . '_driver');

                //if a driver didn't set - skip protocol
                if (!$driver_txt_id) {
                    continue;
                }

                if (!$this->config->get($driver_txt_id . '_status')) {
                    $error = new AError(
                        'Cannot send notification. Communication driver ' . $driver_txt_id . ' is disabled!'
                    );
                    $error->toLog()->toMessages();
                    continue;
                }

                //use safe usage
                $driver_file = DIR_EXT . $driver_txt_id . DS . 'core' . DS . 'lib' . DS . $driver_txt_id . '.php';
                if (!is_file($driver_file)) {
                    $error = new AError('Cannot find file ' . $driver_file . ' to send notification.');
                    $error->toLog()->toMessages();
                    continue;
                }
                try {
                    /** @noinspection PhpIncludeInspection */
                    include_once($driver_file);
                    //if class of driver
                    $classname = preg_replace('/[^a-zA-Z]/', '', $driver_txt_id);
                    if (!class_exists($classname)) {
                        $error = new AError('IM-driver ' . $driver_txt_id . ' load error.');
                        $error->toLog()->toMessages();
                        continue;
                    }

                    $driver = new $classname();
                } catch (Exception $e) {
                    $this->log->write(__CLASS__ . '::' . __METHOD__ . ": " . $e->getMessage());
                }
            }
            //if a driver cannot be initialized - skip protocol
            if ($driver === null) {
                continue;
            }

            $store_name = $this->config->get('store_name') . ": ";
            //send notification to a user
            if (!empty($sendpoint_data[1])) {
                if ($user_im_settings[$protocol]) {
                    $to = $user_im_settings[$protocol];
                    //check is notification for this protocol and sendpoint allowed
                    $message = $msg_details[1]['message'];
                    if (empty($message)) {
                        //send a default message. Not recommended
                        $message = $this->language->get($sendpoint_data[1]['text_key']);
                    }
                    $message = $this->_process_message_text($message);

                    if ($message) {
                        //use safe call
                        try {
                            if ($protocol == 'email') {
                                $driver->send($to, $store_name . $message, $templateTextId, $templateData, $attachments);
                            } else {
                                $driver->send($to, $store_name . $message);
                            }
                        } catch (Exception $e) {
                            $this->log->write(__CLASS__ . '::' . __METHOD__ . ": " . $e->getMessage());
                        }
                    }
                }
            }

            unset($driver);
        }
        $this->registry->set('force_skip_errors', false);
        return true;
    }

    public function getUserSendPointSettings($user_id, $section, $sendpoint, $store_id)
    {
        $user_id = (int)$user_id;
        $store_id = (int)$store_id;

        if (!$user_id || !$sendpoint || !in_array($section, ['admin', 'storefront', ''])) {
            return [];
        }

        $sql = "SELECT *
                FROM " . $this->db->table('user_notifications') . " un
                INNER JOIN " . $this->db->table('users') . " u
                    ON (u.user_id = un.user_id AND u.status = 1)
                WHERE un.user_id=" . $user_id . "
                    AND un.store_id = '" . $store_id . "'
                    AND un.sendpoint = '" . $this->db->escape($sendpoint) . "'";
        if ($section != '') {
            $sql .= " AND un.section = '" . ($section == 'admin' ? 1 : 0) . "'";
        }

        $sql .= "ORDER BY un.protocol";
        $result = $this->db->query($sql);

        $output = [];
        foreach ($result->rows as $row) {
            if (!$output[$row['protocol']]) {
                $output[$row['protocol']] = $row['uri'];
            }
        }
        return $output;
    }

    /**
     * @param array $settings
     *
     * @return bool|null
     * @throws AException
     */
    public function validateUserSettings($settings)
    {
        $this->errors = [];
        if (!$settings) {
            return null;
        }
        //get all installed drivers
        $drivers = $this->getIMDriverObjects(['status' => '']);
        $supported_protocols = array_keys($drivers);
        foreach ($settings as $protocol => $uri) {
            //ignore non-supported protocols
            if (!in_array($protocol, $supported_protocols) || !$uri) {
                continue;
            }
            /**
             * @var AMailIM $driver
             */
            $driver = $drivers[$protocol];
            if (!$driver->validateURI($uri)) {
                $this->errors[$protocol] = implode('<br>', $driver->errors);
            }
        }
        return (!$this->errors);
    }

    public function saveIMSettings($user_id, $section, $sendpoint, $store_id, $settings = [])
    {
        $user_id = (int)$user_id;
        $store_id = (int)$store_id;
        $settings = (array)$settings;
        if (!$user_id || !$sendpoint || !in_array($section, ['admin', 'storefront', ''])) {
            return false;
        }

        foreach ($settings as $protocol => $uri) {
            $sql = "DELETE FROM " . $this->db->table('user_notifications') . "
                    WHERE user_id=" . $user_id . "
                        AND store_id = '" . $store_id . "'";
            if ($section != '') {
                $sql .= " AND section = '" . ($section == 'admin' ? 1 : 0) . "'";
            }
            $sql .= " AND sendpoint = '" . $this->db->escape($sendpoint) . "'
                    AND protocol='" . $this->db->escape($protocol) . "'";

            $this->db->query($sql);

            $sections = $section ? [$section] : ['admin', 'storefront'];
            foreach ($sections as $s) {
                $s = $s == 'admin' ? 1 : 0;
                $sql = "INSERT INTO " . $this->db->table('user_notifications') . "
                            (user_id, store_id, section, sendpoint, protocol, uri, date_added)
                        VALUES ('" . $user_id . "',
                                '" . $store_id . "',
                                '" . $s . "',
                                '" . $this->db->escape($sendpoint) . "',
                                '" . $this->db->escape($protocol) . "',
                                '" . $this->db->escape($uri) . "',
                                NOW())";
                $this->db->query($sql);
            }
        }

        return true;
    }

    /**
     * @return array
     * @throws AException
     */
    public function getIMDriversList()
    {
        $filter = [
            'category' => 'Communication',
        ];
        $extensions = $this->extensions->getExtensionsList($filter);
        $driver_list = [];
        foreach ($extensions->rows as $ext) {
            $driver_txt_id = $ext['key'];

            if ($this->config->get($driver_txt_id . '_status') === null) {
                continue;
            }

            //NOTE! all IM drivers MUST have class by these paths
            try {
                /** @noinspection PhpIncludeInspection */
                include_once(DIR_EXT . $driver_txt_id . DS . 'core' . DS . 'lib' . DS . $driver_txt_id . '.php');
            } catch (Exception $e) {
                $this->log->write(__CLASS__ . '::' . __METHOD__ . ": " . $e->getMessage());
            }
            $classname = preg_replace('/[^a-zA-Z]/', '', $driver_txt_id);

            if (!class_exists($classname)) {
                continue;
            }
            /**
             * @var AMailIM $driver
             */
            $driver = new $classname();
            $driver_list[$driver->getProtocol()][$driver_txt_id] = $driver->getName();
        }
        return $driver_list;
    }
}