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).
}