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/Email.php
<?php

namespace Acelle\Model;

use Illuminate\Database\Eloquent\Model;
use Validator;
use ZipArchive;
use KubAT\PhpSimple\HtmlDomParser;
use Acelle\Model\Setting;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Acelle\Jobs\SendSingleMessage;
use Acelle\Library\Traits\HasTemplate;
use Acelle\Library\Traits\HasUid;
use Acelle\Library\Contracts\HasTemplateInterface;
use Acelle\Library\HtmlHandler\InjectTrackingPixel;
use Acelle\Library\HtmlHandler\TransformUrl;
use Acelle\Library\RouletteWheel;
use Closure;
use Exception;

class Email extends Model implements HasTemplateInterface
{
    use HasTemplate;
    use HasUid;

    // Email types
    public const TYPE_REGULAR = 'regular';
    public const TYPE_PLAIN_TEXT = 'plain-text';

    protected $serversPool = null;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'subject', 'from_email', 'from_name', 'reply_to', 'sign_dkim', 'track_open', 'track_click', 'action_id', 'skip_failed_message',
        'use_default_sending_server_from_email',
    ];

    // Cached HTML content
    protected $parsedContent = null;

    /**
     * Association with mailList through mail_list_id column.
     */
    public function automation()
    {
        return $this->belongsTo('Acelle\Model\Automation2', 'automation2_id');
    }

    /**
     * Get the customer.
     */
    public function customer()
    {
        return $this->belongsTo('Acelle\Model\Customer');
    }

    /**
     * Association with attachments.
     */
    public function attachments()
    {
        return $this->hasMany('Acelle\Model\Attachment');
    }

    /**
     * Association with email links.
     */
    public function emailLinks()
    {
        return $this->hasMany('Acelle\Model\EmailLink');
    }

    /**
     * Association with open logs.
     */
    public function trackingLogs()
    {
        return $this->hasMany('Acelle\Model\TrackingLog');
    }

    public function emailWebhooks()
    {
        return $this->hasMany('Acelle\Model\EmailWebhook');
    }

    /**
     * Get email's associated tracking domain.
     */
    public function trackingDomain()
    {
        return $this->belongsTo('Acelle\Model\TrackingDomain', 'tracking_domain_id');
    }

    public function signature()
    {
        return $this->belongsTo('Acelle\Model\Signature', 'signature_id');
    }

    public function setSignature($signature)
    {
        $this->signature_id = $signature ? $signature->id : null;
        $this->save();
    }

    /**
     * Get email's default mail list.
     */
    public function defaultMailList()
    {
        return $this->automation->mailList();
    }

    /**
     * Create automation rules.
     *
     * @return array
     */
    public function rules($request = null)
    {
        $rules = [
            'subject' => 'required',
            'from_email' => 'required|email',
            'from_name' => 'required',
        ];

        if ($this->use_default_sending_server_from_email) {
            $rules['from_email'] = 'nullable|email';
            $rules['reply_to'] = 'nullable|email';
        } else {
            $rules['from_email'] = 'required|email';
            $rules['reply_to'] = 'required|email';
        }

        // tracking domain
        if (isset($request) && $request->custom_tracking_domain) {
            $rules['tracking_domain_uid'] = 'required';
        }

        return $rules;
    }

    /**
     * Upload attachment.
     */
    public function uploadAttachment($file)
    {
        $file_name = $file->getClientOriginalName();
        $att = $this->attachments()->make();
        $att->size = $file->getSize();
        $att->name = $file->getClientOriginalName();

        $path = $file->move(
            $this->getAttachmentPath(),
            $att->name
        );

        $att->file = $this->getAttachmentPath($att->name);
        $att->save();

        return $att;
    }

    /**
     * Get attachment path.
     */
    public function getAttachmentPath($path = null)
    {
        return $this->customer->getAttachmentsPath($path);
    }

    /**
     * Find and update email links.
     */
    public function updateLinks()
    {
        if (!$this->getTemplateContent()) {
            return false;
        }

        $links = [];

        // find all links from contents
        // Fix: str_get_html returning false
        defined('MAX_FILE_SIZE') || define('MAX_FILE_SIZE', 10000000);
        $document = HtmlDomParser::str_get_html($this->getTemplateContent());
        foreach ($document->find('a') as $element) {
            if (preg_match('/^http/', $element->href) != 0) {
                $links[] = trim($element->href);
            }
        }

        // delete al bold links
        $this->emailLinks()->whereNotIn('link', $links)->delete();

        foreach ($links as $link) {
            $exist = $this->emailLinks()->where('link', '=', $link)->count();

            if (!$exist) {
                $emailLink = $this->emailLinks()->make([
                    'link' => $link,
                ]);

                $emailLink->customer_id = $this->customer_id;
                $emailLink->save();
            }
        }
    }

    public function getServersPool()
    {
        if (is_null($this->serversPool)) {
            // Available sending servers
            $serversAndWeights = $this->automation->mailList->getSendingServers();
            // No sending server ready for delivery
            if (empty($serversAndWeights)) {
                throw new Exception('No sending server avaialble');
            }

            $pool = new RouletteWheel();
            foreach ($serversAndWeights as $serverId => $weight) {
                $server = SendingServer::find($serverId)->mapType();
                $pool->add($server, $weight);
            }

            $this->serversPool = $pool;
        }

        return $this->serversPool;
    }

    public function queueDeliverTo($subscriber, $autoTriggerId, $actionId, $queue)
    {
        $serversPool = $this->getServersPool();

        if (config('app.saas')) {
            $subscription = $this->automation->customer->getCurrentActiveGeneralSubscription();
        } else {
            $subscription = null;
        }

        if (is_null($subscription)) {
            throw new Exception("Customer {$this->customer->name} has no active subscription, quit sending");
        }

        $job = new SendSingleMessage(
            $this,
            $subscriber,
            $serversPool,
            $subscription,
            $autoTriggerId,
            $actionId,
        );

        $connection = config('custom.automation_queue_connection');

        if (!is_null($this->customer->custom_queue_name)) {
            $queue = $this->customer->custom_queue_name;
        } elseif (is_null($queue)) {
            $queue = ACM_QUEUE_TYPE_SINGLE;
            // $queue is currently determined by Automation/Send action
            // in which priority is determined by trigger type
        } else {
            // use the $queue passed to
        }

        if ($connection) {
            // By default, this value is null, meaning that automation should use the default queue connection
            // specified in the .env file (QUEUE_CONNECTION)
            $job->onConnection($connection)->onQueue($queue);
        } else {
            $job->onQueue($queue);
        }

        // @important: the original method dispatch() sometimes returns PendingDispatch object instead of the dispatched JobID
        // So, here we should use safe_dispatch instead
        $jobId = safe_dispatch($job);

        return $jobId;
    }

    public function sendTestEmail($emailAddress)
    {
        $validator = Validator::make([ 'email' => $emailAddress ], [
            'email' => 'required|email',
        ]);

        if ($validator->fails()) {
            $firstErrorMsg = $validator->errors()->first();
            throw new Exception($firstErrorMsg);
        }

        $server = $this->automation->mailList->pickSendingServer();

        // Build a valid message with a fake contact
        // build a temporary subscriber oject used to pass through the sending methods
        $subscriber = $this->createStdClassSubscriber(['email' => $emailAddress]);
        list($message, $msgId) = $this->prepareEmail($subscriber, $server);
        $server->send($message);
    }

    /**
     * Log delivery message, used for later tracking.
     */
    public function trackMessage($response, $subscriber, $server, $msgId, $triggerId = null)
    {
        // @todo: customerneedcheck
        $params = array_merge(array(
            'email_id' => $this->id,
            'message_id' => $msgId,
            'subscriber_id' => $subscriber->id,
            'sending_server_id' => $server->id,
            'customer_id' => $this->automation->customer->id,
            'auto_trigger_id' => $triggerId,
        ), $response);

        if (!isset($params['runtime_message_id'])) {
            $params['runtime_message_id'] = $msgId;
        }

        // create tracking log for message
        $this->trackingLogs()->create($params);
    }

    public function isOpened($subscriber)
    {
        if (is_null($subscriber)) {
            return false;
        }

        return $this->trackingLogs()->where('subscriber_id', $subscriber->id)
                            ->join('open_logs', 'open_logs.message_id', '=', 'tracking_logs.message_id')->exists();
    }

    public function isClicked($subscriber)
    {
        if (is_null($subscriber)) {
            return false;
        }

        return $this->trackingLogs()->where('subscriber_id', $subscriber->id)
                            ->join('click_logs', 'click_logs.message_id', '=', 'tracking_logs.message_id')->exists();
    }

    /**
     * Fill email's fields from request.
     */
    public function fillAttributes($params)
    {
        $this->fill($params);

        // Tacking domain
        if (isset($params['custom_tracking_domain']) && $params['custom_tracking_domain'] && isset($params['tracking_domain_uid'])) {
            $tracking_domain = \Acelle\Model\TrackingDomain::findByUid($params['tracking_domain_uid']);
            if ($tracking_domain) {
                $this->tracking_domain_id = $tracking_domain->id;
            } else {
                $this->tracking_domain_id = null;
            }
        } else {
            $this->tracking_domain_id = null;
        }
    }

    public function isSetup()
    {
        return $this->subject && $this->reply_to && $this->from_email && $this->template;
    }

    public function deleteAndCleanup()
    {
        if ($this->template) {
            $this->template->deleteAndCleanup();
        }

        $this->delete();
    }

    public function logger()
    {
        return $this->automation->logger();
    }

    public function newWebhook()
    {
        $webhook = new \Acelle\Model\EmailWebhook();
        $webhook->email_id = $this->id;

        return $webhook;
    }

    public function isStageExcluded(string $name): bool
    {
        switch ($name) {
            case InjectTrackingPixel::class:
                if ($this->track_open) {
                    return false;
                } else {
                    return true;
                }
                break;

            case TransformUrl::class:
                if ($this->track_click) {
                    return false;
                } else {
                    return true;
                }
                break;

            default:
                // do not exclude by default
                return false;
                break;
        }
    }

    public function copy()
    {
        $copy = $this->replicate();
        $copy->generateUid();
        $copy->template_id = null;
        $copy->save();

        // Notice that ->template is still available even if template_id is set to null
        if ($this->template) {
            $copy->setTemplate($this->template);
        }

        return $copy;
    }

    public static function newDefault()
    {
        $email = new self([
            'sign_dkim' => true,
            'track_open' => true,
            'track_click' => true,
        ]);

        $email->skip_failed_message = Setting::isYes('email.default.skip_failed_message');

        //
        $email->fillDeliveryStatuses($email->getDefaultDeliveryStatuses());

        return $email;
    }

    public function setPreheader($preheader)
    {
        $this->preheader = $preheader;
        $this->save();
    }

    public function removePreheader()
    {
        $this->preheader = null;
        $this->save();
    }

    public function checkDelayFlag()
    {
        return false; // not supported
    }

    public function debug(Closure $callback = null)
    {
        // @important: temporary function to avoid Automation bug
    }

    public function useSendingServerDefaultFromEmailAddress()
    {
        return $this->use_default_sending_server_from_email == true;
    }

    public function fillDeliveryStatuses(array $array)
    {
        $array = array_map(function ($status) {
            $status = trim($status);

            //
            if (empty($status)) {
                throw new \Exception("Status can not be empty");
            }

            //
            if (!in_array($status, [
                \Acelle\Model\Subscriber::VERIFICATION_STATUS_DELIVERABLE,
                \Acelle\Model\Subscriber::VERIFICATION_STATUS_UNDELIVERABLE,
                \Acelle\Model\Subscriber::VERIFICATION_STATUS_UNKNOWN,
                \Acelle\Model\Subscriber::VERIFICATION_STATUS_RISKY,
                \Acelle\Model\Subscriber::VERIFICATION_STATUS_UNVERIFIED,
            ])) {
                throw new \Exception("Status $status is invalid!");
            }

            return $status;
        }, $array);

        $this->delivery_statuses = json_encode($array);
    }

    public function setDeliveryStatuses(array $array)
    {
        $this->fillDeliveryStatuses($array);
        $this->save();
    }

    public function getDefaultDeliveryStatuses()
    {
        return [
            \Acelle\Model\Subscriber::VERIFICATION_STATUS_DELIVERABLE,
            \Acelle\Model\Subscriber::VERIFICATION_STATUS_UNVERIFIED,
        ];
    }

    public function getDeliveryStatuses()
    {
        if ($this->delivery_statuses == null) {
            // Default delivery statuses
            return $this->getDefaultDeliveryStatuses();
        }

        return json_decode($this->delivery_statuses, true);
    }
}