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/ai.naniguide.com/app/Model/AutoTrigger.php
<?php

/**
 * Automation Event Trigger class.
 *
 * Model class for logging triggered events
 *
 * 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 App\Model;

use Illuminate\Database\Eloquent\Model;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;
use Closure;
use Exception;
use App\Library\Traits\HasUid;
use App\Model\Subscriber;

class AutoTrigger extends Model
{
    use HasUid;

    protected $fillable = [
        //
    ];

    protected $casts = [
        'created_at'   => 'datetime',
        'updated_at'   => 'datetime',
        'scheduled_at' => 'datetime',
        'held_at'      => 'datetime',
        'state'        => 'array',
        'history'      => 'array',
    ];

    /**
     * Convenience truthy check — admin has frozen this row.
     * Cron + Send-callback skip held rows; phase + scheduled_at stay intact
     * so Unhold restores execution from where it paused.
     */
    public function isHeld(): bool
    {
        return $this->held_at !== null;
    }

    /**
     * Maximum execution-history entries kept on a single row. Older entries
     * are dropped (FIFO) on overflow. 100 is past the longest plausible flow
     * (~30 nodes); rooms for future loops without unbounded row bloat.
     */
    public const HISTORY_CAP = 100;

    /**
     * Append one execution-history entry to the row.
     *
     * Caller MUST call `save()` after this — the helper only mutates the
     * in-memory column so it can be batched with other column writes (one
     * UPDATE per advance/wait/etc.).
     *
     * Auto-fills `at` (UTC ISO-8601) if absent so callers don't repeat boilerplate.
     * Trims to HISTORY_CAP keeping the most recent entries.
     *
     * @param array{node?:string,type?:string,outcome:string,branch?:?string,until?:?string,error?:?string,phase_after?:string,at?:string} $entry
     */
    public function appendHistory(array $entry): void
    {
        $entry['at'] = $entry['at'] ?? now()->toIso8601String();
        $list = is_array($this->history) ? $this->history : [];
        $list[] = $entry;
        $overflow = count($list) - self::HISTORY_CAP;
        if ($overflow > 0) {
            array_splice($list, 0, $overflow);
        }
        $this->history = $list;
    }

    public static function boot()
    {
        parent::boot();

        // Why ::creating is required here although it was already in HasUid!!!!!!!!
        static::creating(function ($item) {
            if (is_null($item->uid)) {
                $item->generateUid();
            }
        });
    }

    /**
     * Associations.
     *
     * @return the associated subscriber
     */
    public function subscriber()
    {
        return $this->belongsTo('App\Model\Subscriber');
    }

    /**
     * Associations.
     *
     * @return the associated automation2
     */
    public function automation2()
    {
        return $this->belongsTo('App\Model\Automation2');
    }

    /**
     * Associations.
     *
     * @return the associated timelines
     */
    public function timelines()
    {
        return $this->hasMany('App\Model\Timeline')->orderBy('created_at', 'DESC');
    }

    /**
     * Filter for rows in the Failed terminal state.
     * (Replaces the legacy `scopeError` that filtered on the dropped is_error
     * boolean — phase = 'failed' is the canonical signal now.)
     */
    public function scopeFailed($query)
    {
        return $query->where('phase', \App\Domain\Automation\Runtime\Phase::Failed->value);
    }

    // Phase 2 cleanup: removed legacy state-machine methods (check / withUpdateLock /
    // updateAction / updateExecutedIndex / traverseExecutedPathWithLastPendingAction /
    // getExecutedActions / getActions / getAction / updateWorkflow / getJson / getTrigger /
    // getNextAction / recordToTimeline). The v2 runtime advances state via
    // FlowExecutor::advance() and reads/writes the explicit columns
    // (current_node_id / scheduled_at / state / phase / error) directly.

    public function logger()
    {
        $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message%\n");

        if (getenv('LOGFILE') != false) {
            $stream = new RotatingFileHandler(getenv('LOGFILE'), 0, Logger::DEBUG);
        } else {
            $logfile = storage_path('logs/' . php_sapi_name() . '/trigger-of-'.$this->automation2->uid.'.log');
            ;
            $stream = new RotatingFileHandler($logfile, 0, Logger::INFO);
        }

        $stream->setFormatter($formatter);

        $pid = getmypid();
        $logger = new Logger($pid);
        $logger->pushHandler($stream);

        return $logger;
    }

    // Phase 2 cleanup: removed getActionById / isActionExecuted / isComplete /
    // findLastActionToExecute / getLatestAction. The v2 runtime tracks "where am I
    // in the flow" via the explicit current_node_id column on this row.

    /** True iff this row reached the Completed phase (flow drained successfully). */
    public function isComplete(): bool
    {
        return $this->phase === \App\Domain\Automation\Runtime\Phase::Completed->value;
    }

    /** True iff the row is in the Failed terminal phase. */
    public function isFailed(): bool
    {
        return $this->phase === \App\Domain\Automation\Runtime\Phase::Failed->value;
    }

    // Phase 2 cleanup: removed legacy soft-state helpers (setError / cacheSubscriberInfo /
    // getSubscriberCachedInfo). The is_error boolean and cached_subscriber JSON columns
    // were dropped — failure surfaces via phase = 'failed' + the error column;
    // subscriber attributes are read live from the Subscriber row (deletion
    // mid-flow → handler observes null and the executor transitions to Failed).
}