HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: //home/xedaptot/be.naniguide.com/app/Model/FeedbackLoopHandler.php
<?php

/**
 * FeedbackLoopHandler class.
 *
 * Model class for feedback loop handler
 *
 * LICENSE: This product includes software developed at
 * the Acelle Co., Ltd. (http://acellemail.com/).
 *
 * @category   MVC Model
 *
 * @author     N. Pham <[email protected]>
 * @author     L. Pham <[email protected]>
 * @copyright  Acelle Co., Ltd
 * @license    Acelle Co., Ltd
 *
 * @version    1.0
 *
 * @link       http://acellemail.com
 */

namespace Acelle\Model;

use Acelle\Library\Traits\HasUid;

class FeedbackLoopHandler extends DeliveryHandler
{
    use HasUid;
    protected $connection = 'mysql';

    protected $logfile = 'feedback-loop-handler';

    // Available feedback types
    // Reference https://en.wikipedia.org/wiki/Abuse_Reporting_Format
    public const FEEDBACK_TYPES = ['abuse', 'fraud', 'virus', 'other', 'not-spam'];
    public const EXCLUDED_FEEDBACK_TYPES = ['not-spam'];

    /**
     * Process feedback message, extract the feedback information (comply to RFC).
     *
     * @return mixed
     */
    public function processMessage($mbox, $msgNo)
    {
        try {
            // Get subject
            $info = imap_headerinfo($mbox, $msgNo);
            $subject = isset($info->subject) ? $info->subject : '[no subject]';

            // Sometimes the Subject is encoded like this: =?UTF-8?B?WWFob28gZW1haWzCoHZlcmlmaWNhdGlvbsKgY29kZQ==?=
            // Use imap_utf8() to safely decode it
            $subject = imap_utf8($subject);
            $this->logger()->info("Message: {$subject}");

            // Get headers & body
            $header = imap_fetchheader($mbox, $msgNo);
            $body = imap_body($mbox, $msgNo, FT_PEEK);

            // Sometimes the header is included in the body of the feedback message
            // So, search for feedback type in both header and body
            $feedbackType = $this->getFeedbackType($header) ?: $this->getFeedbackType($body);

            if (is_null($feedbackType)) {
                imap_setflag_full($mbox, $msgNo, '\\Seen \\Flagged');
                throw new \Exception('Cannot find Feedback-Type header, not an ARF message. Skipped');
            }

            if (in_array($feedbackType, self::EXCLUDED_FEEDBACK_TYPES)) {
                imap_setflag_full($mbox, $msgNo, '\\Seen \\Flagged');
                throw new \Exception('Feedback-Type is "not-spam". Ignored.');
            }

            $msgId = $this->getMessageId($body);

            if (empty($msgId)) {
                imap_setflag_full($mbox, $msgNo, '\\Seen \\Flagged');
                throw new \Exception('cannot find Message-ID, skipped');
            }

            $customerUid = \Acelle\Library\StringHelper::extractCustomerUidFromMessageId($messageId);
            $customer = \Acelle\Model\Customer::findByUid($customerUid);
            if (!is_null($customer)) {
                $customer->setUserDbConnection();
            }

            $this->logger()->info('Processing abuse notification for message '.$msgId);

            $trackingLog = TrackingLog::where('message_id', $msgId)->first();
            if (empty($trackingLog)) {
                imap_setflag_full($mbox, $msgNo, '\\Seen \\Flagged');
                throw new \Exception('message-id not found in tracking_logs, skipped');
            }

            // record a feedback log, one message may have more than one
            $feedbackLog = new FeedbackLog();
            $feedbackLog->customer_id = $trackingLog->customer_id;
            $feedbackLog->tracking_log_id = $trackingLog->id;
            $feedbackLog->message_id = $msgId;
            $feedbackLog->feedback_type = $feedbackType;
            $feedbackLog->raw_feedback_content = $header.PHP_EOL.$body;
            $feedbackLog->save();

            // just delete the feedback notification email
            imap_delete($mbox, $msgNo);

            $this->logger()->info('Feedback recorded for message '.$msgId);
        } catch (\Exception $ex) {
            $this->logger()->warning($ex->getMessage());
        }
    }

    /**
     * Extract FeedbackType from feedback email.
     *
     * @return mixed
     */
    public function getFeedbackType($header)
    {
        preg_match('/(?<=Feedback-Type:)\s*[^\s]*/', $header, $matched);
        if (sizeof($matched) == 0) {
            return;
        }

        $type = strtolower(trim($matched[0]));
        return $type;
    }

    /**
     * Get all items.
     *
     * @return collect
     */
    public static function getAll()
    {
        return self::select('*');
    }

    /**
     * Filter items.
     *
     * @return collect
     */
    public static function filter($request)
    {
        $user = $request->user();
        $query = self::select('feedback_loop_handlers.*');

        // Keyword
        if (!empty(trim($request->keyword))) {
            foreach (explode(' ', trim($request->keyword)) as $keyword) {
                $query = $query->where(function ($q) use ($keyword) {
                    $q->orwhere('feedback_loop_handlers.name', 'like', '%'.$keyword.'%')
                        ->orWhere('feedback_loop_handlers.type', 'like', '%'.$keyword.'%')
                        ->orWhere('feedback_loop_handlers.host', 'like', '%'.$keyword.'%');
                });
            }
        }

        // filters
        $filters = $request->all();
        if (!empty($filters)) {
            if (!empty($filters['type'])) {
                $query = $query->where('feedback_loop_handlers.type', '=', $filters['type']);
            }
        }

        if (!empty($request->admin_id)) {
            $query = $query->where('feedback_loop_handlers.admin_id', '=', $request->admin_id);
        }

        return $query;
    }

    /**
     * Search items.
     *
     * @return collect
     */
    public static function search($request)
    {
        $query = self::filter($request);

        if (!empty($request->sort_order)) {
            $query = $query->orderBy($request->sort_order, $request->sort_direction);
        }

        return $query;
    }

    /**
     * Items per page.
     *
     * @var array
     */
    public static $itemsPerPage = 25;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'host', 'port', 'username', 'password', 'protocol', 'email', 'encryption',
    ];

    /**
     * Get validation rules.
     *
     * @return object
     */
    public static function rules()
    {
        return [
            'name' => 'required',
            'host' => 'required',
            'port' => 'required',
            'username' => 'required',
            'password' => 'required',
            'protocol' => 'required',
            'email' => 'required|email',
        ];
    }

    /**
     * Get select options.
     *
     * @return array
     */
    public static function getSelectOptions()
    {
        $query = self::getAll();

        $options = $query->orderBy('name', 'asc')->get()->map(function ($item) {
            return ['value' => $item->id, 'text' => $item->name];
        });

        return $options;
    }

    /**
     * Protocol select options.
     *
     * @return array
     */
    public static function protocolSelectOptions()
    {
        return [
            ['value' => 'imap', 'text' => 'imap'],
        ];
    }

    /**
     * Encryption select options.
     *
     * @return array
     */
    public static function encryptionSelectOptions()
    {
        return [
            ['value' => 'tls', 'text' => 'tls'],
            ['value' => 'starttls', 'text' => 'starttls'],
            ['value' => 'notls', 'text' => 'notls'],
            ['value' => 'ssl', 'text' => 'ssl'],
        ];
    }
}