/** * @constructor * * @param {array} $rules Redirect rules * @param {callable|string} $rules[][source] Regex, plain string startsWith() or callback matcher func, * @param {string} $rules[][target] String for redirection, can use backreference on regex, * @param {?int} $rules[][options] Redirection $options, or internal by default, * @param {?string} $options[source] Base path to match against requests, defaults to root. * @param {string|callable} $options[target] Redirects to a static target, or function($request) returns a string; */ public function __construct($rules) { // rewrite all URLs if (is_string($rules)) { $rules = array('*' => $rules); } $rules = util::wrapAssoc($rules); $this->rules = array_reduce($rules, function ($result, $rule) { $rule = array_select($rule, array('source', 'target', 'options')); // note: make sure source is callback if (is_string($rule['source'])) { // regex if (@preg_match($rule['source'], null) !== false) { $rule['source'] = matches($rule['source']); if (is_string($rule['target'])) { $rule['target'] = compose(invokes('uri', array('path')), replaces($rule['source'], $rule['target'])); } } else { if (!is_callable($rule['source'])) { $rule['source'] = startsWith($rule['source']); if (is_string($rule['target'])) { $rule['target'] = compose(invokes('uri', array('path')), replaces('/^' . preg_quote($rule['source']) . '/', $rule['target'])); } } } } if (!is_callable($rule['source'])) { throw new InvalidArgumentException('Source must be string, regex or callable.'); } $result[] = $rule; return $result; }, array()); }
/** * @protected * * Delete all relations. */ function afterDelete() { // Delete jobs array_map(invokes('delete'), $this->getInstances()); return parent::afterDelete(); }
/** * Process next task with POST data. */ public function process() { // note: Some tasks can work without post data, but request method must be POST. if (!$this->__isSuperUser && $this->__request->method() != 'post') { $this->__response->status(405); // Method not allowed return; } // WorkInstance if (!$this->identity()) { $this->__response->status(404); // WorkInstance not found return; } // TaskInstance $instance = $this->nextTask(); if (!$instance) { $this->__response->status(404); // TaskInstance not foudn return; } // release mutable lock for work instance initialization. $this->_immutable = false; // remove tasks to prevent unwanted change. $tasks = $this->tasks; unset($this->tasks); // creates $this->dataStore if not yet. if (empty($this->dataStore)) { $this->dataStore = array(); } unset($this->lastError); // immutable marker to prevent direct modifications to the internal data. $this->_immutable = true; // note: Send bayeux message to groups with permission to this task. $userGroups = $instance->userGroups(); try { // Note: Since $this->dataStore is an array, it is mutable itself. $promise = $instance->process(); } catch (\Exception $e) { Log::warning('Task process exception.', array_filter(array('message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTrace()))); $lastError = array('message' => $this->__response->__($e->getMessage()), 'code' => $e->getCode()); // note: Failure on Headless tasks will revert to previous task. if (@$instance->type == 'Headless') { $deferred = new Deferred(); $deferred->reject($lastError['message'], $lastError['code']); $promise = $deferred->promise(); unset($deferred); } else { $this->_immutable = false; $this->lastError = $lastError; } unset($lastError); } $this->_immutable = false; $result = array(); $saveFunc = function () use(&$result) { unset($this->timestamp); $this->save($result); }; if (isset($promise)) { // note: rejection here means revert to previous task $promise->fail(function ($error, $code = null) use($instance, $tasks) { $this->lastError = array_filter(array('message' => $error, 'code' => $code)); // revert to previous task $prevTask = array_search($instance->identity(), array_map(invokes('identity'), $tasks)); $prevTask = @$tasks[$prevTask - 1]; // fallback to the first task if (!$prevTask) { $prevTask = reset($tasks); } $this->nextTask = util::packUuid($prevTask->identity()); }); // note: resolution always advances to next task $promise->done(function () use($instance, $tasks) { $nextTask = array_search($instance->identity(), array_map(invokes('identity'), $tasks)); $nextTask = @$tasks[$nextTask + 1]; if ($nextTask) { $this->nextTask = util::packUuid($nextTask->identity()); } else { $this->state = static::STATE_CLOSE; $this->nextTask = null; } }); // note: controller script must call resolve() or reject() to make this happen. $promise->always($saveFunc); } else { $saveFunc(); } unset($saveFunc); // note: Merge user groups before and after task processing if ($this->nextTask) { $userGroups = array_unique(array_merge($userGroups, $this->nextTask()->userGroups())); } foreach ($userGroups as $userGroup) { Bayeux::sendMessage("/group/{$userGroup}", array('action' => 'update', '@collection' => 'WorkInstance', 'timestamp' => $this->timestamp)); } if (@$result['error']) { $this->__response->status(500); return $result; } else { // note; User might no longer has access to the updated work instance. if ($this->data()) { $this->__response->status(200); return $this; } else { $this->__response->status(204); } } }