/** * @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()); }
function process() { $path = (array) @$this->taskInstance()->settings; $path = $path['path']; $store = $this->dataStore(); Log::info(sprintf('Value of (%s) in current data store.', $path), array('path' => $path, 'value' => util::deepVal($path, $store))); }
/** * Shorthand access to common filter types. */ static function &commonFilters() { static $filters; if (!$filters) { $filters = array('raw' => array('filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_NULL_ON_FAILURE), 'rawS' => array('filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE), 'rawA' => array('filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE), 'boolS' => array('filter' => FILTER_VALIDATE_BOOLEAN, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE), 'intS' => array('filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE), 'intA' => array('filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE), 'floatS' => array('filter' => FILTER_VALIDATE_FLOAT, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE), 'floatA' => array('filter' => FILTER_VALIDATE_FLOAT, 'flags' => FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE), 'strS' => array('filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE | FILTER_FLAG_NO_ENCODE_QUOTES), 'strA' => array('filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE | FILTER_FLAG_NO_ENCODE_QUOTES), 'urlS' => array('filter' => FILTER_VALIDATE_URL, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE), 'urlA' => array('filter' => FILTER_VALIDATE_URL, 'flags' => FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE), 'date' => array('filter' => FILTER_CALLBACK, 'flags' => FILTER_NULL_ON_FAILURE, 'options' => '\\core\\Utility::validateDateTime'), 'dateS' => array('filter' => FILTER_CALLBACK, 'flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE, 'options' => '\\core\\Utility::validateDateTime'), 'priceS' => array('filter' => FILTER_CALLBACK, 'options' => function ($input) { return preg_match('/[+-]?\\d+(?:\\.\\d+)?(?:\\:\\w+)?/', trim(Utility::unwrapAssoc($input))) ? $input : null; }), 'regex' => function ($pattern) { return array('filter' => FILTER_CALLBACK, 'options' => function ($input) use($pattern) { return preg_match($pattern, $input) ? $input : null; }); }); } return $filters; }
public function validate(array &$errors = array()) { $schema = $this->schema(); if (!Jsv4::isValid($this->data, $schema)) { // try to coerce on initial failure $result = Jsv4::coerce($this->data, $schema); if ($result->value) { $this->data = $result->value; } // return errors if exists if (!empty($result->errors)) { $errors = array_merge(util::objectToArray($result->errors)); } } return parent::validate(); }
public function resolve(Request $request, Response $response) { global $argv; // Debug access log if (System::environment() == 'debug') { switch ($request->client('type')) { case 'cli': $message = implode(' ', array($request->client('type'), $request->uri())); break; default: $message = implode(' ', array($request->client('version'), strtoupper($request->method()), $request->uri('path'))); @Log::debug($message, array_filter(array('origin' => $request->client('referer'), 'userAgent' => util::cascade(@$request->client('userAgent'), 'Unknown'), 'timeElapsed' => round(microtime(1) - $request->timestamp(), 4) . ' secs'))); break; } } }
/** * @param $handlers An array of template handler options, with the following format: * [{ render: // Template rendering function: function($template, $resource); * , extensions: // File extensions to match against handling templates. * }] */ public function __construct($handlers = array()) { $handlers = Utility::wrapAssoc($handlers); foreach ($handlers as &$handler) { if (!is_callable($handler['render'])) { throw new FrameworkException('Please specify a valid render function.'); } if (!@$handler['extensions']) { throw new FrameworkException('Please specify file extensions this handler handles.'); } if (is_string($handler['extensions'])) { $handler['extensions'] = preg_split('/\\s+/', $handler['extensions']); } $handler['extensions'] = Utility::wrapAssoc($handler['extensions']); } $this->handlers = $handlers; }
public function __invoke(array $record) { if ($record['level'] >= $this->level) { // note: file and line should be one level above the called class. $backtrace = util::getCallee($this->backtraceLevel - 1); if (isset($backtrace['file'])) { $record['extra']['file'] = str_replace(getcwd(), '', basename($backtrace['file'], '.php')); } if (isset($backtrace['line'])) { $record['extra']['line'] = $backtrace['line']; } $backtrace = util::getCallee($this->backtraceLevel); $action = @"{$backtrace['class']}{$backtrace['type']}{$backtrace['function']}"; if ($action) { $record['extra']['action'] = $action; } unset($action); } return $record; }
public function render($path) { $res = $this->response(); $mime = util::getInfo($path); if (preg_match('/^text\\//', $mime)) { $res->header('Content-Type', "{$mime}; charset=utf-8"); } else { $res->header('Content-Type', $mime); } unset($mime); // note; during developement everything must be revalidated if (System::environment() == System::ENV_DEVELOPMENT) { $res->isVirtual = true; } parent::render($path); // Ouptut the file if ($res->status() < 300) { $res->header('Content-Length', filesize($path)); $res->send($path); } }
/** * Open an image file. */ function open($file) { $stat = Utility::getInfo($file, FILEINFO_MIME_TYPE); switch ($stat) { case 'image/jpeg': case 'image/jpg': $image = imagecreatefromjpeg($file); break; case 'image/gif': $image = imagecreatefromgif($file); break; case 'image/png': $image = imagecreatefrompng($file); imagealphablending($image, true); imagesavealpha($image, true); break; case 'image/bmp': // $image = imagecreatefromwbmp($file); $image = self::importBMP($file); break; case 'image/vnd.wap.wbmp': $image = imagecreatefromwbmp($file); break; case 'image/tif': case 'image/tiff': default: $image = null; break; } if (!$image) { throw new exceptions\CoreException("Invalid image format \"{$stat}\", this class supports a limited set of image formats. Read the code for more details."); } imageinterlace($image, 1); $this->image = $image; $this->mime = $stat; }
function triggerDeprecate($successor = '') { $message = Utility::getCallee(); $message = implode('::', array_filter([@$message['class'], @$message['function']])); $message = "Function {$message}() has been deprecated"; if ($successor) { $message .= ", use its successor {$successor} instead"; } trigger_error("{$message}.", E_USER_DEPRECATED); }
/** * Perform cURL requests and throw appropriate exceptions. * * An array of parameters used in a curl_setopt_array, * multiple calls can be passed in. * * This function make use of curl_multi no matter it is * single request or not. * * Callbacks are used to handle results inside the array. * * $option['callbacks'] = array( * 'progress' => [Function] * , 'success' => [Function] * , 'failure' => [Function] * , 'always' => [Function] * ); * * @return void */ public static function curlRequest($options) { $options = Utility::wrapAssoc(array_values((array) $options)); $multiHandle = curl_multi_init(); // Initialize cUrl options array_walk($options, function (&$option) { // 1. Request headers $option['response'] = array('headers' => ''); $option[CURLOPT_HEADERFUNCTION] = function ($curl, $data) use(&$option) { $option['response']['headers'] .= $data; return strlen($data); }; // 2. Progress function $progressCallback =& $option['callbacks']['progress']; if ($progressCallback) { $option[CURLOPT_NOPROGRESS] = false; $option[CURLOPT_PROGRESSFUNCTION] = function () use(&$progressCallback) { if (func_num_args() == 4) { list($dSize, $dLen, $uSize, $uLen) = func_get_args(); } else { list($req, $dSize, $dLen, $uSize, $uLen) = func_get_args(); } if ($dSize || $dLen) { static $_dLen = 0; if ($_dLen != $dLen) { $_dLen = $dLen; /*! Note by Vicary @ 2.Oct.2012 * Total download size is often 0 if server doesn't * response with a Content-Length header. * * Total size guessing logic: * 1. if $dLen < 1M, assume 1M. * 2. if $dLen < 10M, assume 10M. * 3. if $dLen < 100M, assume 100M. * 4. if $dLen < 1G, assume 1G. */ if (!$dSize) { // Do not assume when size under 1K if ($dLen < 5000) { return; } elseif ($dLen < 10000000) { $dSize = 20000000; } elseif ($dLen < 100000000) { $dSize = 200000000; } elseif ($dLen < 1000000000) { $dSize = 2000000000; } else { $dSize = 20000000000; } // $dSize = $dLen / .05; } // Download progress, from 0 to 1. $progressArgs = array($dLen / $dSize, $dLen, $dSize); } } else { if ($uSize) { static $_uLen = 0; if ($_uLen != $uLen) { $_uLen = $uLen; $uSize *= -1; $uLen += $uSize; // Upload progress, from -1 to 0. $progressArgs = array($uLen / $uSize, $uLen, $uSize); } } } // Fire the event for each µSeconds. static $_tOffset = 0; $tOffset = microtime(1); if (isset($progressArgs) && $tOffset - $_tOffset > self::progressInterval()) { $_tOffset = $tOffset; Utility::forceInvoke($progressCallback, $progressArgs); } }; } unset($progressCallback); // 3. Apply cUrl options, numeric keys only. $option['handle'] = curl_init(); curl_setopt_array($option['handle'], array_filter_keys($option, 'is_int')); }); $requestIndex = 0; while ($requestIndex < self::$maximumRequests && isset($options[$requestIndex])) { curl_multi_add_handle($multiHandle, $options[$requestIndex++]['handle']); } // Start the multi request do { $status = curl_multi_exec($multiHandle, $active); /* Added by Vicary @ 6.Nov.2012 Blocks until there is a message arrives. */ curl_multi_select($multiHandle); do { $info = curl_multi_info_read($multiHandle, $queueLength); if ($info === FALSE) { continue; } $optionIndex = array_search($info['handle'], array_map(prop('handle'), $options)); if ($optionIndex === FALSE) { continue; } $curlOption =& $options[$optionIndex]; $callbacks =& $curlOption['callbacks']; // Success handler if ($info['result'] === CURLE_OK) { // Fire a 100% downloaded event. if (@$callbacks['progress']) { Utility::forceInvoke($callbacks['progress'], array(1, 1, 1)); usleep(self::progressInterval() * 1000000); } // Append HTTP status code $curlOption['status'] = curl_getinfo($info['handle'], CURLINFO_HTTP_CODE); Utility::forceInvoke(@$callbacks['success'], array(curl_multi_getcontent($info['handle']), $curlOption)); } else { $errorNumber = curl_errno($info['handle']); $errorMessage = curl_error($info['handle']); // libcurl errors, try to parse it. if ($errorNumber === 0) { if (preg_match('/errno: (\\d+)/', $errorMessage, $matches)) { $errorNumber = (int) $matches[1]; $curlErrors = unserialize(FRAMEWORK_NET_CURL_ERRORS); if (isset($curlErrors[$errorNumber])) { $errorMessage = $curlErrors[$errorNumber]; } } } Utility::forceInvoke(@$callbacks['failure'], array($errorNumber, $errorMessage, $curlOption)); unset($errorNumber, $errorMessage); } // Always handler Utility::forceInvoke(@$callbacks['always'], array($curlOption)); if (isset($options[$requestIndex])) { curl_multi_add_handle($multiHandle, $options[$requestIndex++]['handle']); // Keep the loop alive. $active = TRUE; } curl_multi_remove_handle($multiHandle, $info['handle']); curl_close($info['handle']); unset($info, $callbacks, $curlOption, $options[$optionIndex], $optionIndex); } while ($queueLength > 0); } while ($status === CURLM_CALL_MULTI_PERFORM || $active); curl_multi_close($multiHandle); }
/** * 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); } } }
AND `pid` IS NOT NULL AND `pid` NOT IN (' . Utility::fillArray($pids) . ')', $pids); if ($res) { $affectedRows += $res->rowCount(); } // Delete cron process only when current time is ahead of start_time $res = Database::query('DELETE FROM `' . FRAMEWORK_COLLECTION_PROCESS . '` WHERE `type` = \'cron\' AND `pid` = 0 AND `pid` NOT IN (' . Utility::fillArray($pids) . ')', $pids); if ($res) { $affectedRows += $res->rowCount(); } // Clear pid of dead permanent process $res = Database::query('UPDATE `' . FRAMEWORK_COLLECTION_PROCESS . '` SET `pid` = NULL WHERE `type` = \'permanent\' AND `pid` IS NOT NULL AND `pid` NOT IN (' . Utility::fillArray($pids) . ')', $pids); if ($res) { $affectedRows += $res->rowCount(); } } unset($res, $pids); if ($affectedRows) { Log::debug(sprintf('Process cleanup, %d processes removed.', $affectedRows)); } die; } // Cron processes if (@$opts['cron']) { Log::debug('Cron started process.'); $scheduler = function ($schedule) { $nextTime = CronExpression::factory($schedule['schedule'])->getNextRunDate()->format('Y-m-d H:i:s');
/** * @protected * * Pack UUID for delete filters. */ function beforeDelete(array &$filter = array()) { if (isset($filter[$this->primaryKey()])) { $filter[$this->primaryKey()] = util::packUuid($filter[$this->primaryKey()]); } return $this; }
/** * @constructor * * @param {?string|array} $options An array of request options, or the URI string. * @param {?string} $options['prefix'] Parameters keys start with this prefix * will be treated as meta-parameters and * not returned in param() related functions. * * Parameters values start with this prefix * will be tried to parsed as constants and * booleans. * * This defaults to the "@" character. * * @param {?string} $options['uri'] The request uri, defaults to $_SERVER['REQUEST_URI']. * @param {?string} $options['method'] Request method, defaults to $_SERVER['REQUEST_METHOD']. * @param {?array} $options['headers'] Request headers, defaults to the contents of getallhheaders() * if the function is available. * @param {?array} $options['client'] Request client details, defaults to everything from $_SERVER. * @param {?array} $options['get'] GET parameters in array format. * @param {?array} $options['post'] POST parameters in array format. * @param {?array} $options['cookies'] COOKIES in array format. * @param {?array} $options['files'] Upload files along with this request. (Use with care) * When doing CURL requests, files array must compatible with CURL. * Otherwise this must obey the resolver-request format. * @param {?string} $options['locale'] Requesting locale, defaults to en_US. */ public function __construct($options = array()) { global $argv; if ($options instanceof Resolver) { $this->resolver = $options; $options = array(); } if (@$options) { if (is_string($options)) { $options = array('uri' => $options); } // Special parameter prefix if (!empty($options['prefix'])) { $this->metaPrefix = $options['prefix']; } // Request URI if (empty($options['uri'])) { throw new FrameworkException('Request URI is required.'); } else { $this->setUri($options['uri']); } // Request method if (isset($options['method'])) { $this->method = strtolower($options['method']); } else { $this->method = 'get'; } // Request headers if (isset($options['headers'])) { $this->headers = (array) $options['headers']; } // Request client if (!empty($options['client'])) { $this->client = (array) $options['client']; } // Request parameters GET if (isset($options['get'])) { $this->paramCache['get'] = (array) $options['get']; } // Request parameters POST if (!empty($options['post'])) { $this->paramCache['post'] = (array) $options['post']; } // Cookies if (isset($options['cookies'])) { $this->paramCache['cookies'] = (array) $options['cookies']; } // File uploads if (isset($options['files'])) { $this->paramCache['files'] = (array) $options['files']; } // Request locale if (!empty($options['locale'])) { $this->locale = (string) $options['locale']; } } else { // Request client switch (constant('PHP_SAPI')) { case 'cli': $this->client = array('type' => 'cli', 'host' => gethostname(), 'user' => get_current_user()); break; default: $this->client = array_filter(array('type' => 'http', 'secure' => @$_SERVER['HTTPS'] && strtolower($_SERVER['HTTPS']) != 'off', 'address' => @$_SERVER['REMOTE_ADDR'], 'host' => @$_SERVER['REMOTE_HOST'], 'port' => @$_SERVER['REMOTE_PORT'], 'user' => @$_SERVER['REMOTE_USER'], 'referer' => @$_SERVER['HTTP_REFERER'], 'version' => @$_SERVER['SERVER_PROTOCOL'], 'userAgent' => @$_SERVER['HTTP_USER_AGENT'], 'forwarder' => @$_SERVER['HTTP_X_FORWARDED_FOR'], 'isAjax' => strtolower(@$_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'), compose('not', 'is_null')); break; } // Request method switch ($this->client('type')) { case 'cli': $this->method = 'cli'; break; default: $this->method = strtolower(@$_SERVER['REQUEST_METHOD']); break; } // Request headers switch ($this->client('type')) { case 'cli': break; default: $this->headers = getallheaders(); break; } // Request parameters switch ($this->client('type')) { case 'cli': $this->paramCache['cli'] = new Optimist(); break; default: // Request parameters GET $this->paramCache['get'] = $_GET; // Request parameters POST if (preg_match('/^application\\/json/', $this->header('Content-Type'))) { $this->paramCache['post'] = ContentDecoder::json(file_get_contents('php://input'), true); } else { $this->paramCache['post'] = $_POST; } // Cookies $this->paramCache['cookies'] =& $_COOKIE; // File uploads if ($this->method() == 'put') { $this->paramCache['files'] = new RequestPutFile($this->header('Content-Type')); } else { util::filesFix(); $parseFile = function ($file) use(&$parseFile) { if (!is_array($file)) { return $file; } if (util::isAssoc($file)) { switch ($file['error']) { case UPLOAD_ERR_OK: return new RequestPostFile($file); case UPLOAD_ERR_NO_FILE: // Skip it. break; default: return $file['error']; } } else { return array_mapdef($file, $parseFile); } }; $this->paramCache['files'] = array_mapdef(array_filter_keys($_FILES, compose('not', startsWith('@'))), $parseFile); unset($parseFile); } break; } // Request URI // CLI requires request parameters switch ($this->client('type')) { case 'cli': /*! Note @ 9 May, 2015 * Usage: node-cli [OPTIONS] COMMAND * Only one command is supported, simply shift it out. */ $this->uri = @$this->paramCache['cli']['_'][0]; if (!$this->uri) { $this->uri = $argv[1]; } break; default: $uri = array('scheme' => $this->client('secure') ? 'https' : 'http', 'user' => @$_SERVER['REMOTE_USER'], 'host' => @$_SERVER['SERVER_NAME'], 'port' => @$_SERVER['SERVER_PORT'], 'path' => @$_SERVER['REQUEST_URI'], 'query' => $_GET); if (empty($uri['user'])) { $uri['user'] = @$_SERVER['PHP_AUTH_USER']; } $this->setUri(array_filter($uri)); // = parse_url(http_build_url($this->uri)); break; } // Parse special parameter values array_walk_recursive($this->paramCache, function (&$value) { if (is_string($value) && strpos($value, $this->metaPrefix) === 0) { $_value = substr($value, strlen($this->metaPrefix)); switch (strtolower($_value)) { case 'true': $value = true; break; case 'false': $value = false; break; default: if (defined($_value)) { $value = constant($_value); } break; } } }); // Request timestamp $this->timestamp = (double) @$_SERVER['REQUEST_TIME_FLOAT']; } // Unified params ($_REQUEST mimic) switch ($this->client('type')) { case 'cli': // $this->paramCache['request'] = $this->paramCache['cli']; break; default: $this->paramCache['request'] = array_merge((array) @$this->paramCache['cookies'], (array) @$this->paramCache['get'], (array) @$this->paramCache['post']); break; } // Failover in case of request time not exists. if (!$this->timestamp) { $this->timestamp = microtime(1); } }
public function resolve(Request $req, Response $res) { $auth = $this->paths; $pathNodes = trim($req->uri('path'), '/'); if ($pathNodes) { $pathNodes = explode('/', $pathNodes); } else { $pathNodes = ['/']; } $lastWildcard = @$auth['*']; foreach ($pathNodes as $index => $pathNode) { if (!util::isAssoc($auth)) { break; // No more definitions, break out. } if (isset($auth['*'])) { $lastWildcard = $auth['*']; } if (isset($auth[$pathNode])) { $auth = $auth[$pathNode]; } else { unset($auth); break; } } if (!isset($auth) || !is_bool($auth) && (!is_array($auth) || util::isAssoc($auth))) { if (empty($lastWildcard)) { throw new FrameworkException('Unable to resolve authentication chain from request URI.'); } else { $auth = $lastWildcard; } } unset($pathNodes, $lastWildcard); // Numeric array if (is_array($auth) && !util::isAssoc($auth)) { $auth = array_reduce($auth, function ($result, $auth) use($req) { if (!$result) { return $result; } if (is_callable($auth)) { $auth = $auth($req); } else { if (is_string($auth)) { if (strpos($auth, '/') === false) { $auth = "authenticators\\{$auth}"; } if (is_a($auth, 'framework\\interfaces\\IAuthenticator', true)) { $result = $result && $auth::authenticate($req); } else { throw new FrameworkException('Unknown authenticator type, must be ' . 'instance of IAuthenticator or callable.'); } } else { throw new FrameworkException('Unknown authenticator type, must be ' . 'instance of IAuthenticator or callable.'); } } return $result && $auth; }, true); } // Boolean if (is_bool($auth) && !$auth) { $res->status($this->statusCode); } // TODO: Mark allowed or denied according to the new resolver mechanism. }
function invokes($name, array $args = array()) { return function ($object) use($name, $args) { if (Utility::isAssoc($object)) { $func = @$object[$name]; } else { if (method_exists($object, $name)) { $func = array($object, $name); } else { if (isset($object->{$name}) && is_callable($object->{$name})) { $func = $object->{$name}; } else { if (is_object($object)) { $object = get_class($object); } if (is_array($object)) { $object = 'input array'; } trigger_error("No callable {$name}() found in {$object}.", E_USER_WARNING); unset($object); } } } return call_user_func_array($func, $args); }; }
/** * Retrieve process related info by specified property $name. * * @param {string} $name Target property in process object, omit this to get the whole object. */ public static function get($name = null) { if (constant('PHP_SAPI') != 'cli' || !function_exists('posix_getppid')) { return null; } $processData =& self::$_processData; if (!$processData) { $processData = Node::get(array(Node::FIELD_COLLECTION => FRAMEWORK_COLLECTION_PROCESS, 'pid' => [getmypid(), posix_getppid()])); $processData = util::unwrapAssoc($processData); } if (is_null($name)) { return $processData; } else { return @$processData[$name]; } }
/** * Generate a one-time authentication token string for additional * security for AJAX service calls. * * Each additional call to this function overwrites the token generated last time. * * @return One-time token string, or null on invalid session. */ static function generateToken($sid = null) { $res = static::ensure($sid); if ($res !== true) { return $res; } $res =& static::$currentSession; $res['token'] = Database::fetchField("SELECT UNHEX(REPLACE(UUID(),'-',''));"); unset($res['timestamp']); if (Node::set($res) === false) { return null; } return util::unpackUuid($res['token']); }
<?php ob_start(); //error_reporting(-1); //ini_set('display_errors','1'); use core\Utility as U; use core\Router; require_once './core/Utility.php'; U::init(); require_once U::$APP . 'routes.php'; Router::execute($_GET['route'], $_POST); exit;
/** * Returns the fileinfo expression of current file. * * @param {int} $type One of the FILEINFO_* constants. * @return {array|string|boolean} Result of finfo_file($type), or false when not applicable. */ public function getInfo($type = FILEINFO_MIME_TYPE) { return \core\Utility::getInfo($this->getRealPath(), $type); }
/** * Primary task when including PHP is that we need * to change $_SERVER variables to match target file. */ private function handle($path, $request, $response) { $context = array('request' => $request, 'response' => $response); $mime = Utility::getInfo($path, FILEINFO_MIME_TYPE); if (strpos($mime, ';') !== false) { $mime = substr($mime, 0, strpos($mime, ';')); } switch ($mime) { // note; special case, need content encoding header here. fall over to static file. case 'image/svg+xml': if (pathinfo($path, PATHINFO_EXTENSION) == 'svgz') { $response->header('Content-Encoding: gzip'); } // mime-types that we output directly. // mime-types that we output directly. case 'application/pdf': case 'application/octect-stream': case 'image/jpeg': case 'image/jpg': case 'image/gif': case 'image/png': case 'image/bmp': case 'image/vnd.wap.wbmp': case 'image/tif': case 'image/tiff': case 'text/plain': case 'text/html': default: $renderer = new StaticFileRenderer($context); break; case 'application/x-php': $renderer = new IncludeRenderer($context); break; } $renderer->render($path); }
public function resolve(Request $request, Response $response) { $path = $this->srcPath . $request->uri('path') . '.url'; // Check if target file is a proxy. if (!is_file($path)) { return; } $cacheTarget = parse_ini_file($path); $cacheTarget = @$cacheTarget['URL']; unset($path); if (!$cacheTarget) { Log::warning('Proxy file has not URL parameter.', array('requestUri' => $request->uri(), 'proxyFile' => $request->uri('path') . '.uri')); $response->status(502); // Bad Gateway return; } /*! Cache Header Notes * * # Cache-Control * [public | private] Cacheable when public, otherwise the client is responsible for caching. * [no-cache( \w+)?] When no fields are specified, the whole thing must revalidate everytime, * otherwise cache it except specified fields. * [no-store] Ignore caching and pipe into output. * [max-age=\d+] Seconds before this cache is meant to expire, this overrides Expires header. * [s-maxage=\d+] Overrides max-age and Expires header, behaves just like max-age. * (This is for CDN and we are using it.) * [must-revalidate] Tells those CDNs which are intended to serve stale contents to revalidate every time. * [proxy-revalidate] Like the "s-" version of max-age, a "must-revalidate" override only for CDN. * [no-transform] Some CDNs will optimize images and other formats, this "opt-out" of it. * * # Expires * RFC timestamp for an absolute cache expiration, overridden by Cache-Control header. * * # ETag * Hash of anything, weak ETags is not supported at this moment. * * # vary * Too much fun inside and we are too serious about caching, ignore this. * * # pragma * This guy is too old to recognize. * [no-cache] Only this is known nowadays and is already succeed by Cache-Control: no-cache. * */ // note; Use "cache-meta://" scheme for header and cache meta info, for performance. // 1. Check if cache exists. $cache = (array) Cache::get("cache-meta://{$cacheTarget}"); // Cache expiration, in seconds. // expires = ( s-maxage || max-age || Expires ); if (@$cache['expires'] && time() > $cache['expires']) { Cache::delete("cache-meta://{$cacheTarget}"); Cache::delete("cache://{$cacheTarget}"); $cache = null; } // - If not exists, make normal request to remote server. // - If exists, make conditional request to remote server. // - Revalidation, we can skip this request and serve the content if false. // revalidates = ( Cache-Control:proxy-revalidate || Cache-Control:must-revalidate ) if (!$cache || @$cache['revalidates']) { $_request = array('uri' => $cacheTarget); if ($cache) { // Last-Modified if (@$cache['headers']['Last-Modified']) { $_request['headers']['If-Modified-Since'] = $cache['Last-Modified']; } // Entity-Tag if (@$cache['headers']['ETag'] && strpos($cache['headers']['ETag'], 'W\\') !== 0) { $_request['headers']['If-None-Match'] = $cache['ETag']; } } else { $cache = array(); } // Make the request $_response = new Response(array('autoOutput' => false)); (new Request($_request))->send(null, $_response); unset($_request); // parse headers into cache settings. if (in_array($_response->status(), array(200, 304))) { $res = preg_split('/\\s*,\\s*/', util::unwrapAssoc($_response->header('Cache-Control'))); $res = array_reduce($res, function ($res, $value) { // todo; Take care of no-cache with field name. if (strpos($value, '=') > 0) { $value = explode('=', $value); $res[$value[0]] = $value[1]; } else { $res[$value] = true; } return $res; }, array()); // private, no-store, no-cache if (@$res['private'] || @$res['no-store'] || @$res['no-cache']) { // note; in case the upstream server change this to uncacheable Cache::delete("cache-meta://{$cacheTarget}"); Cache::delete("cache://{$cacheTarget}"); $_response->clearBody(); } if ($_response->status() == 200 && $_response->body()) { $cache['contents'] = $_response->body(); } // expires = ( s-maxage || max-age || Expires ); if (@$res['s-maxage']) { $cache['expires'] = time() + $res['s-maxage']; } elseif (@$res['max-age']) { $cache['expires'] = time() + $res['max-age']; } else { $res = util::unwrapAssoc($_response->header('Expires')); if ($res) { $cache['expires'] = strtotime($res); } } // revalidates = ( Cache-Control:proxy-revalidate || Cache-Control:must-revalidate ) if (@$res['proxy-revalidate'] || @$res['must-revalidate']) { $cache['revalidates'] = true; } unset($res); } $cache['headers'] = array_map('core\\Utility::unwrapAssoc', $_response->header()); // PHP does not support chunked, skip this one. unset($cache['headers']['Transfer-Encoding']); // note; If cache is to be ignored, the $cacheTarget variable will be already unset(). if (isset($cacheTarget)) { if (@$cache['contents']) { Cache::set("cache://{$cacheTarget}", $cache['contents']); } Cache::set("cache-meta://{$cacheTarget}", array_filter_keys($cache, isNot('contents'))); } unset($_response); } // note; Send cache headers regardless of the request condition. if (@$cache['headers']) { $response->clearHeaders(); foreach ($cache['headers'] as $name => $value) { $response->header($name, $value, true); } unset($name, $value); } // note; Handles conditional request $ch = array_map('core\\Utility::unwrapAssoc', (array) @$cache['headers']); $mtime = @$ch['Last-Modified'] ? strtotime($ch['Last-Modified']) : false; // Request headr: If-Modified-Since if (@$ch['Last-Modified'] && $mtime) { if (strtotime($request->header('If-Modified-Since')) >= $mtime) { return $response->status(304); } } // Request header: If-Range if ($request->header('If-Range')) { // Entity tag if (strpos(substr($request->header('If-Range'), 0, 2), '"') !== false && @$ch['ETag']) { if ($this->compareETags(@$ch['ETag'], $request->header('If-Range'))) { return $this->response()->status(304); } } elseif (strtotime($request->header('If-Range')) === $mtime) { return $this->response()->status(304); } } unset($mtime); // Request header: If-None-Match if (!$request->header('If-Modified-Since') && $request->header('If-None-Match')) { // Exists but not GET or HEAD switch ($request->method()) { case 'get': case 'head': break; default: return $this->response()->status(412); } /*! Note by Vicary @ 24 Jan, 2013 * If-None-Match means 304 when target resources exists. */ if ($request->header('If-None-Match') === '*' && @$ch['ETag']) { return $this->response()->status(304); } if ($this->compareETags(@$ch['ETag'], preg_split('/\\s*,\\s*/', $request->header('If-None-Match')))) { return $this->response()->status(304); } } // Request header: If-Match if (!$request->header('If-Modified-Since') && $request->header('If-Match')) { // Exists but not GET or HEAD switch ($request->method()) { case 'get': case 'head': break; default: return $this->response()->status(412); } if ($request->header('If-Match') === '*' && !@$ch['ETag']) { return $this->response()->status(412); } preg_match_all('/(?:^\\*$|(:?"([^\\*"]+)")(?:\\s*,\\s*(:?"([^\\*"]+)")))$/', $request->header('If-Match'), $eTags); // 412 Precondition Failed when nothing matches. if (@$eTags[1] && !in_array($eTag, (array) $eTags[1])) { return $this->response()->status(412); } } if ($cacheTarget && empty($cache['contents'])) { $cache['contents'] = Cache::get("cache://{$cacheTarget}"); } // Output the cahce content $response->send($cache['contents'], 200); }
/** * Get a list of data models from the collection. */ function find(array $filter = array()) { if ($filter && !util::isAssoc($filter)) { $filter = array($this->_primaryKey => $filter); } $filter[Node::FIELD_COLLECTION] = self::collectionName(); $collection = array(); Node::getAsync($filter, function ($data) use(&$collection) { // create a new instance for retrieved data $model = get_called_class(); $model = new $model($data); if (isset($this->__request)) { $model->__request = $this->__request; } if (isset($this->__response)) { $model->__response = $this->__response; } // force invoke internal function util::forceInvoke(array($model, 'afterLoad')); // add if model still has data if ($model->data()) { $collection[] = $model; } }); return $collection; }
private static function walkArray($node, &$writer, $ns = NULL, $currentNode = NULL) { $ns = (array) $ns; // Text value if (!is_array($node)) { if (strlen($node) > self::CDATA_THRESHOLD) { $writer->writeCData($node); } else { $writer->text($node); } return; } $nodeStarted = FALSE; if ($currentNode !== NULL) { //------------------------------ // startElement() //------------------------------ // 1. Metadata exists // 2. Not numeric array if (Utility::isAssoc($node)) { self::startElement($currentNode, $writer, $ns); //------------------------------ // Metadata //----------------------------- // @attributes if (isset($node['@attributes'])) { foreach ($node['@attributes'] as $key => $value) { if (preg_match('/^(xmlns\\:?)(.*)$/', $key, $matches)) { $ns[$matches[2]] = $value; } $key = explode(':', $key); if (count($key) == 2) { $writer->writeAttributeNS($key[0], $key[1], $ns[$key[0]], $value); } else { $writer->writeAttribute($key[0], $value); } } unset($node['@attributes']); } // @comments if (isset($node['@comments'])) { foreach ((array) $node['@comments'] as $value) { $writer->writeComment($value); } unset($node['@comments']); } $nodeStarted = TRUE; } } if (isset($node['@value'])) { $node = (array) $node['@value']; } //------------------------------ // Children //------------------------------ if (Utility::isAssoc($node)) { foreach ($node as $key => $value) { if (!is_array($value)) { self::writeElement($key, $writer, $ns, $value); } else { self::walkArray($value, $writer, $ns, $key); } } } else { foreach ($node as $value) { if (!$nodeStarted && !is_array($value)) { self::writeElement($currentNode, $writer, $ns, $value); } else { self::walkArray($value, $writer, $ns, $currentNode); } } } if ($nodeStarted) { $writer->endElement(); } }
private function processArgs() { if ($this->result) { return; } $argv = $this->argv; // Remove until the script itself. if (@$_SERVER['PHP_SELF'] && in_array($_SERVER['PHP_SELF'], $argv)) { $argv = array_slice($argv, array_search($_SERVER['PHP_SELF'], $argv) + 1); } $args = array(); $currentNode =& $args['_']; foreach ($argv as $value) { // Match flag style, then assign initial value. if (preg_match('/^\\-\\-?([\\w\\.]+)(?:=(.*))?$/', $value, $matches)) { $nodePath = explode('.', $matches[1]); $currentNode =& $args[array_shift($nodePath)]; while ($nodePath) { $currentNode =& $currentNode[array_shift($nodePath)]; } $value = isset($matches[3]) ? $matches[3] : TRUE; // Value exists if ($currentNode) { if (!is_array($currentNode)) { $currentNode = array($currentNode); } $currentNode[] = $value; } else { $currentNode = $value; } unset($matches, $nodePath); } else { if (isset($currentNode)) { if ($currentNode === TRUE) { $currentNode = $value; } else { array_pop($currentNode); $currentNode[] = $value; } } else { @($args['_'][] = $value); } unset($currentNode); } } unset($value); if (!$args['_']) { unset($args['_']); } //------------------------------ // Alias //------------------------------ foreach ($this->alias as $key => $alias) { // Alias: type if (isset($this->types[$key])) { $this->types += array_fill_keys($alias, $this->types[$key]); } // Alias: defaults if (isset($this->defaults[$key])) { $this->defaults += array_fill_keys($alias, $this->defaults[$key]); } } unset($key, $alias); // Alias: value foreach ($args as $option => $value) { $target = (array) @$this->alias[$option]; foreach ($target as $alias) { $args[$alias] = $value; } unset($alias); } unset($option, $value, $target); if (@$args['_']) { foreach ($args['_'] as $value) { $target = (array) @$this->alias[$value]; foreach ($target as $alias) { $args['_'][] = $alias; } unset($alias); } $args['_'] = array_unique($args['_']); } unset($value, $target); //------------------------------ // Demands //------------------------------ if (is_numeric($this->demand)) { if (count((array) @$args['_']) < $this->demand) { $this->showError("It requires at least {$this->demand} args to run."); die; } } else { $missingKeys = array(); foreach ($this->demand as $key => $value) { if (!isset($args[$key])) { $missingKeys[] = $key; } } if ($missingKeys) { $this->showError("Missing required options: " . implode(', ', $missingKeys)); die; } } //------------------------------ // Type-casting //------------------------------ $args = Utility::flattenArray($args); array_walk($args, function (&$value, $key) { if (isset($this->types[$key])) { if (!settype($value, $this->types[$key])) { $this->showError("Unable to cast {$key} into type {$type}."); die; } } elseif (is_numeric($value)) { $value = doubleval($value); } }); $args += (array) $this->defaults; return $this->result = Utility::unflattenArray($args); }