descriptive names 1 => 'job_number', 2 => 'date', ); // regexp for fetching queue info protected static string $queueRegex = '/^(\d+)\s+([\w\d- :]+) (\w) ([\w-]+)$/'; // another regexp matching for queue data protected static array $queueMap = array( 1 => 'job_number', 2 => 'date', 3 => 'queue', 4 => 'user', ); protected static string $res = 'CRON: '; protected static string $pipeTo = '2>&1'; // redirects STDERR to STDOUT (redundant) protected static array $atSwitches = array( // supported AT options list 'queue' => '-q', 'list_queue' => '-l', 'file' => '-f', 'remove' => '-d', ); /** * cmd() -- public static function * * @users self::addCommand * * @param $command * @param $time * @param null $queue * @return mixed */ static public function cmd($command, $time, $queue = null) { return self::addCommand($command, $time, $queue); } /** * @uses self::addFile * * @param $file * @param $time * @param null $queue * @return mixed */ static public function file($file, $time, $queue = null) { return self::addFile($file, $time, $queue); } /** * @uses self::listQueue * * @param null $queue * @return mixed */ static public function lq($queue = null) { return self::listQueue($queue); } /** * Add a job to the `at` queue * @param string $command * @param string $time see `man at` * @param string $queue a-zA-Z see `man at` * @return mixed */ static private function addCommand($command, $time, $queue = null) { $command = self::escape($command); $time = self::escape($time); $exec_string = "echo '$command' | " . self::$binary; if(null !== $queue) { $exec_string .= ' ' . self::$atSwitches['queue'] . " {$queue[0]}"; } $exec_string .= " $time "; return self::addJob($exec_string); } /** * Add a file job to the `at` queue * @param string $file Full path to the file to be executed * @param string $time see `man at` * @param string $queue a-zA-Z see `man at` * @return mixed */ static private function addFile($file, $time, $queue = null) { $file = self::escape($file); $time = self::escape($time); $exec_string = self::$binary . ' ' . self::$atSwitches['file'] . " $file"; if(null !== $queue) { $exec_string .= ' ' . self::$atSwitches['queue'] . " {$queue[0]}"; } $exec_string .= " $time "; return self::addJob($exec_string); } /** * Return a list of the jobs currently in the queue. If you do not specify * a queue to look at then it will return all jobs in all queues. * @param string $queue * @return array of Job objects * @return mixed */ static private function listQueue($queue = null) { $exec_string = self::$binary . ' ' . self::$atSwitches['list_queue']; if(null !== $queue) { $exec_string .= ' ' . self::$atSwitches['queue'] . " {$queue[0]}"; } $result = self::exec($exec_string); return self::transform($result, 'queue'); } /** * Remove a job by job number * @param int $job_number * @return Boolean */ static public function removeJob($job_number) { $rc = true; if (empty($job_number)) { $hdr = sprintf(INFO_LOC, __METHOD__, __LINE__); consoleLog(static::$res, CON_ERROR, $hdr . RES_ATW . ERROR_DATA_INPUT_EMPTY . STRING_JOB_NUMBER); return(false); } $job_number = self::escape($job_number); // $exec_string = self::$binary . ' ' . self::$atSwitches['remove'] . " $job_number"; $exec_string = 'atrm ' . $job_number; $output = self::exec($exec_string); if(count($output)) { $rc = false; foreach ($output as $errorMessage) { $hdr = sprintf(INFO_LOC, __METHOD__, __LINE__); consoleLog(static::$res, CON_ERROR, $hdr . $errorMessage); } echo getDateTime() . CON_ERROR . RES_ATW . $output[0] . PHP_EOL; } return($rc); } /** * Add a job to the at queue and return the * @param string $job_exec_string * @return mixed */ static private function addJob($job_exec_string) { $output = self::exec($job_exec_string); return (count($output) == 1) ? $output[0] : $output[1]; // $job = self::transform($output); // if(!count($job)) { // $logger = new gacErrorLogger(); // $logger->warn('failed to add job to the queue. Exec command: ' . $job_exec_string); // $logger->__destruct(); // } // return reset($job); } /** * Transform the output of `at` into an array of objects * @param array $output_array * @param string $type Is this an add or list we are transforming? * @return array An array of Job objects */ static private function transform(array $output_array, string $type = 'add'):array { $jobs = array(); // Get the appropriate regex class property for the type // of `at` switch/command being run at this point in time. $regex = $type . 'Regex'; $regex = self::$$regex; $map = $type .'Map'; $map = self::$$map; foreach($output_array as $line) { $matches = array(); @preg_match($regex, $line, $matches); if(count($matches) > count($map)) { $jobs[] = self::mapJob($matches, $map); } } return $jobs; } /** * Map the details matched with the regex to descriptively named properties * in a new Job object * @param array $details * @param array $map * @return Job */ static private function mapJob($details, $map) { $Job = new Job(); foreach($details as $key => $detail) { if(isset($map[$key])) { $Job->$map[$key] = $detail; } } return $Job; } /** * Escape a string that will be passed to exec * @param string $string * @return string */ static private function escape($string) { return escapeshellcmd($string); } /** * Run the command via exec() and return each line of the output as an * array * @param string $string * @return array Each line of output is an element in the array */ static private function exec($string) { $output = array(); $string .= ' ' . self::$pipeTo; exec($string, $output); return $output; } } /** * A simple class for storing a jobs details and some methods for manipulating * it. A job model if you will. * * @author Simon Holywell * @version 16.11.2010 */ class Job { /** * Data store for the job details * @var array */ public array $data = array(); protected string $res = 'CRON: '; protected /** @noinspection PhpMissingFieldTypeInspection */ $date; /** * Magic method to set a value in the $data * property of the class * @param string $name * @param mixed $value */ public function __set($name, $value) { $this->data[$name] = $value; } /** * Magic method to get a value in the $data property * of the class * @param string $name * @return mixed */ public function __get($name) { if (isset($this->data[$name])) { return $this->data[$name]; } $logger = new gacErrorLogger(); $trace = debug_backtrace(); $logger->warn("Undefined property via __get(): $name in $trace[0]['file'] . on line $trace[0]['line']"); $logger->__destruct(); return(false); } /** * Magic method to check for the existence of an * index in the $data property of the class * @param string $name * @return bool */ public function __isset($name) { return isset($this->data[$name]); } /** * Magic method to unset an index in the $data property * of the class * @param string $name */ public function __unset($name) { unset($this->data[$name]); } /** * Remove this job from the queue */ public function remove() { if(isset($this->job_number)) { gacATWrapper::removeJob((int)$this->job_number); } } /** * Get a DateTime object for date and time extracted from * the output of `at` * @example echo $job->date()->format('d-m-Y'); * @uses DateTime * @return DateTime A PHP DateTime object */ public function date(): ?DateTime { try { return new DateTime($this->date); } catch (Exception | TypeError $t) { $hdr = sprintf(INFO_LOC, __METHOD__, __LINE__); consoleLog($this->res, CON_ERROR, $hdr . $t->getMessage()); return null; } } }