/** * Encode the input parameter $data as JSON and output it with appropriate http headers. * * @param mixed $data input variable, takes origin, age, methods * @param array $params parameters for the http headers: ttl, origin, methods (GET, POST, PUT, DELETE) * @param bool $output send the output headers and body? or return them? * * @return array (array headers, string body) * * @see http://www.w3.org/TR/2008/WD-access-control-20080912/ */ public static function json(array $data, array $params = [], bool $output = true) { $f3 = \Base::instance(); $headers = array_key_exists('headers', $params) ? $params['headers'] : []; $headers['Content-type'] = 'application/json; charset=utf-8'; $ttl = array_key_exists('ttl', $params) ? $params['ttl'] : 0; // cache for $ttl seconds if (empty($ttl)) { $f3->expire(0); //$headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'; $ttl = 0; } $headers['Expires'] = Time::http(array_key_exists('expires', $params) ? $params['expires'] : time() + $ttl); $headers['Access-Control-Max-Age'] = $ttl; $headers['Access-Control-Allow-Methods'] = array_key_exists('http_methods', $params) ? $params['http_methods'] : ''; //'OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE'; $headers['Access-Control-Allow-Origin'] = array_key_exists('acl_origin', $params) ? $params['acl_origin'] : '*'; $headers['Access-Control-Allow-Credentials'] = array_key_exists('acl_credentials', $params) ? $params['credentials'] : 'false'; $body = json_encode($data, JSON_PRETTY_PRINT); if (!empty($output)) { $headers['Content-Length'] = \UTF::instance()->strlen($body); } $headers['ETag'] = array_key_exists('etag', $params) ? $params['etag'] : md5($body); if (empty($output)) { return ['headers' => $headers, 'body' => $body]; } else { // send the headers + data foreach ($headers as $header => $value) { if (is_string($value) && empty($value)) { continue; } header($header . ': ' . $value); } if (!array_key_exists('cookie', $params)) { header_remove('Set-Cookie'); // prevent php session } // default status is 200 - OK $f3->status(array_key_exists('http_status', $params) ? $params['http_status'] : 200); $method = $f3->get('VERB'); switch ($method) { case 'HEAD': break; default: case 'GET': case 'PUT': case 'POST': case 'DELETE': echo $body; } } }
/** * generate random string * * @param int $length of password * @param string $chars characters to use for random string * @return string password */ public static function random(int $length = 10, string $chars = null) : string { if (empty($chars)) { // ignore characters which can be consued, i, l, 1, o, O, 0 etc $chars = '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWYZ'; } $chars = str_shuffle($chars); // shuffle base character string $x = \UTF::instance()->strlen($chars) - 1; $str = ''; for ($i = 0; $i < $length; $i++) { $str .= \UTF::instance()->substr($chars, rand(0, $x), 1); } return (string) $str; }
/** * Utility to convert timestamp into a http header date/time. * * @param int time php time value * @param string $zone timezone */ public static function HTTP(int $unixtime = null, string $zone = '') : string { $f3 = \Base::instance(); // use current time if bad time value or unset $unixtime = (int) $unixtime; if (0 >= $unixtime) { $unixtime = time(); } // if its not a 3 letter timezone set it to GMT if (3 == \UTF::instance()->strlen($zone)) { $zone = strtoupper($zone); } else { $zone = ''; } return \UTF::instance()->trim(gmdate('D, d M Y H:i:s', $unixtime) . ' ' . $zone); }
/** * Return an f3 URL route by its alias or the given path * converting 2nd argument $params to querystring if array * * @param string $url name of the url alias if beginning with @ or relative * * @param mixed $params url params as string or array * @param boolean $full default true * @return string */ public static function internal(string $url, $params = null, bool $full = true) : string { $f3 = \Base::instance(); if ('@' == $url[0]) { $url = $f3->alias(\UTF::instance()->substr($url, 1)); } // missing slash at start of url path if ('/' !== \UTF::instance()->substr($url, 0, 1)) { $url = '/' . $url; } if (!empty($params)) { $session_name = strtolower(session_name()); if (array_key_exists($session_name, $params)) { unset($params[$session_name]); } if (is_array($params)) { $params = http_build_query($params); } if (!empty($params)) { $url .= '?' . $params; } } return empty($full) ? $url : $f3->get('SCHEME') . '://' . $f3->get('HOST') . $url; }
/** * Bind value to key * @return mixed * @param $key string * @param $val mixed */ function set($key, $val) { $fields = $this->fieldConf; unset($this->fieldsCache[$key]); // pre-process if field config available if (!empty($fields) && isset($fields[$key]) && is_array($fields[$key])) { // handle relations if (isset($fields[$key]['belongs-to-one'])) { // one-to-many, one-to-one if (is_null($val)) { $val = NULL; } elseif (is_object($val) && !($this->dbsType == 'mongo' && $val instanceof \MongoId)) { // fetch fkey from mapper if (!$val instanceof Cortex || $val->dry()) { trigger_error(self::E_INVALID_RELATION_OBJECT); } else { $relConf = $fields[$key]['belongs-to-one']; $rel_field = is_array($relConf) ? $relConf[1] : '_id'; $val = $val->get($rel_field, true); } } elseif ($this->dbsType == 'mongo' && !$val instanceof \MongoId) { $val = new \MongoId($val); } } elseif (isset($fields[$key]['has-one'])) { $relConf = $fields[$key]['has-one']; if (is_null($val)) { $val = $this->get($key); $val->set($relConf[1], NULL); } else { if (!$val instanceof Cortex) { $rel = $this->getRelInstance($relConf[0], null, $key); $rel->load(array('_id = ?', $val)); $val = $rel; } $val->set($relConf[1], $this->_id); } $this->saveCsd[$key] = $val; return $val; } elseif (isset($fields[$key]['belongs-to-many'])) { // many-to-many, unidirectional $fields[$key]['type'] = self::DT_JSON; $relConf = $fields[$key]['belongs-to-many']; $rel_field = is_array($relConf) ? $relConf[1] : '_id'; $val = $this->getForeignKeysArray($val, $rel_field, $key); } elseif (isset($fields[$key]['has-many'])) { // many-to-many, bidirectional $relConf = $fields[$key]['has-many']; if ($relConf['hasRel'] == 'has-many') { // custom setter $val = $this->emit('set_' . $key, $val); $val = $this->getForeignKeysArray($val, '_id', $key); $this->saveCsd[$key] = $val; // array of keys return $val; } elseif ($relConf['hasRel'] == 'belongs-to-one') { // TODO: many-to-one, bidirectional, inverse way trigger_error("not implemented"); } } // convert array content if (is_array($val) && $this->dbsType == 'sql') { if ($fields[$key]['type'] == self::DT_SERIALIZED) { $val = serialize($val); } elseif ($fields[$key]['type'] == self::DT_JSON) { $val = json_encode($val); } else { trigger_error(sprintf(self::E_ARRAY_DATATYPE, $key)); } } // add nullable polyfill if ($val === NULL && ($this->dbsType == 'jig' || $this->dbsType == 'mongo') && array_key_exists('nullable', $fields[$key]) && $fields[$key]['nullable'] === false) { trigger_error(sprintf(self::E_NULLABLE_COLLISION, $key)); } // MongoId shorthand if ($this->dbsType == 'mongo' && !$val instanceof \MongoId) { if ($key == '_id') { $val = new \MongoId($val); } elseif (preg_match('/INT/i', $fields[$key]['type']) && !isset($fields[$key]['relType'])) { $val = (int) $val; } } if (preg_match('/BOOL/i', $fields[$key]['type'])) { $val = !$val || $val === 'false' ? false : (bool) $val; if ($this->dbsType == 'sql') { $val = (int) $val; } } } // fluid SQL if ($this->fluid && $this->dbsType == 'sql') { $schema = new Schema($this->db); $table = $schema->alterTable($this->table); // add missing field if (!in_array($key, $table->getCols())) { // determine data type if (isset($this->fieldConf[$key]) && isset($this->fieldConf[$key]['type'])) { $type = $this->fieldConf[$key]['type']; } elseif (is_int($val)) { $type = $schema::DT_INT; } elseif (is_double($val)) { $type = $schema::DT_DOUBLE; } elseif (is_float($val)) { $type = $schema::DT_FLOAT; } elseif (is_bool($val)) { $type = $schema::DT_BOOLEAN; } elseif (date('Y-m-d H:i:s', strtotime($val)) == $val) { $type = $schema::DT_DATETIME; } elseif (date('Y-m-d', strtotime($val)) == $val) { $type = $schema::DT_DATE; } elseif (\UTF::instance()->strlen($val) <= 255) { $type = $schema::DT_VARCHAR256; } else { $type = $schema::DT_TEXT; } $table->addColumn($key)->type($type); $table->build(); // update mapper fields $newField = $table->getCols(true); $newField = $newField[$key]; $refl = new \ReflectionObject($this->mapper); $prop = $refl->getProperty('fields'); $prop->setAccessible(true); $fields = $prop->getValue($this->mapper); $fields[$key] = $newField + array('value' => NULL, 'changed' => NULL); $prop->setValue($this->mapper, $fields); } } // custom setter $val = $this->emit('set_' . $key, $val); return $this->mapper->set($key, $val); }
/** * setup the base application environment. * * @param \Base $f3 * @return void */ public static function start() { $f3 = \Base::instance(); // http://php.net/manual/en/function.ob-start.php ob_start(); // read config and overrides // @see http://fatfreeframework.com/framework-variables#configuration-files $f3->config('config/default.ini'); // by default this file does not exist, you must create it and include your local settings if (file_exists('config/config.ini')) { $f3->config('config/config.ini'); } // set home directory for project $f3->set('HOMEDIR', realpath($f3->get('SERVER.DOCUMENT_ROOT') . '/../')); // make sure directories are full, not relative path foreach (['LOGS', 'TEMP', 'UPLOADS'] as $key) { $dir = $f3->get($key); if (!empty($dir)) { $dir = realpath($dir); $f3->set($key, $dir . '/'); } } // these take multiple paths $language = $f3->get('LANGUAGE'); foreach (['LOCALES', 'UI'] as $key) { $paths = $f3->get($key); // remove any invalid dirs if (!empty($paths)) { $dirs = $f3->split($paths); foreach ($dirs as $k => $dir) { if (empty($dir)) { unset($dirs[$k]); continue; } // switch for different language templates $langDir = ''; if ('UI' == $key) { $langDir = 'templates/' . $language . '/' . $dir; $dir = 'templates/en/' . $dir; if (!file_exists($langDir)) { unset($langDir); } } if (!empty($langDir)) { $dirs[$k] = realpath($langDir) . '/' . ';' . realpath($dir) . '/'; } else { $dirs[$k] = realpath($dir) . '/'; } } $f3->set($key, join(';', $dirs)); } } $debug = $f3->get('debug'); // default cacheable data time in seconds from config $ttl = $f3->get('ttl.default'); // enable full logging if not production $logfile = $f3->get('log.file'); if (empty($logfile)) { $f3->set('log.file', '/dev/null'); } elseif ('production' !== $f3->get('app.env')) { ini_set('log_errors', 'On'); $logfile = $f3->get('LOGS') . $logfile; $f3->set('logfile', $logfile); ini_set('error_log', $logfile); ini_set('error_reporting', -1); ini_set('ignore_repeated_errors', 'On'); ini_set('ignore_repeated_source', 'On'); } // parse params for http-style dsn // setup database connection params // @see http://fatfreeframework.com/databases $httpDSN = $f3->get('db.dsn_http'); if (!empty($httpDSN)) { $dbParams = $f3->get('db'); $params = \FFMVC\Helpers\DB::instance()->parseHttpDsn($httpDSN); $params['dsn'] = \FFMVC\Helpers\DB::instance()->createDbDsn($params); $dbParams = array_merge($dbParams, $params); $f3->set('db', $dbParams); } // setup outgoing email server for php mail command ini_set('SMTP', $f3->get('email.host')); ini_set('sendmail_from', $f3->get('email.from')); ini_set('smtp_port', $f3->get('email.port')); ini_set('user', $f3->get('email.user')); ini_set('password', $f3->get('email.pass')); if (PHP_SAPI !== 'cli') { return; } // log errors if run on command line ini_set('display_errors', 'On'); ini_set('error_log', 'On'); ini_set('html_errors', 'Off'); // set default error handler output for CLI mode $f3->set('ONERROR', function ($f3) { $e = $f3->get('ERROR'); // detailed error notifications because it's not public $errorMessage = sprintf("Trace: %s\n\nException %d: %s\n%s\n", $e['trace'], $e['code'], $e['status'], $e['text']); echo $errorMessage; if (\Registry::exists('logger')) { $logger = \Registry::get('logger'); if (is_object($logger)) { $logger->write($errorMessage, $f3->get('log.date')); } } }); // fix for f3 not populating $_GET when run on the command line $uri = $f3->get('SERVER.REQUEST_URI'); $querystring = preg_split("/&/", \UTF::instance()->substr($uri, 1 + \UTF::instance()->strpos($uri . '&', '?'))); if (!empty($querystring) && count($querystring)) { foreach ($querystring as $pair) { if (0 == count($pair)) { continue; } $val = preg_split("/=/", $pair); if (!empty($val) && count($val) == 2) { $k = $val[0]; $v = $val[1]; if (!empty($k) && !empty($v)) { $_GET[$k] = $v; } } } $f3->set('GET', $_GET); } }
function Unicode() { $this->set('title', 'Unicode'); $str = 'אני יכול לאכול זכוכית וזה לא מזיק לי'; $this->expect(is_null($this->get('ERROR')), 'No errors expected at this point', 'ERROR variable is set: ' . $this->get('ERROR.text')); $this->expect(UTF::strlen($str) == 36, 'String length evaluated correctly', 'String length is wrong: ' . UTF::strlen($str)); $this->expect(UTF::substr($str, 0, 5) == 'אני י', 'Substring at offset 0 (length 5) is correct', 'Substring at offset 0 (length 5) is wrong: ' . UTF::substr($str, 0, 5)); $this->expect(UTF::substr($str, 12) == 'ול זכוכית וזה לא מזיק לי', 'Substring at offset 12 (unspecified length) is correct', 'Substring at offset 12 (unspecified length) is wrong: ' . UTF::substr($str, 12)); $this->expect(UTF::substr($str, 3, 4) == ' יכו', 'Substring at offset 3 (length 4) is correct', 'Substring at offset 3 (length 4) is wrong: ' . UTF::substr($str, 3, 4)); $this->expect(UTF::substr('', 7) == FALSE, 'Substring of empty string returns FALSE', 'Substring of empty string returns non-boolean value: ' . UTF::substr('', 0)); $this->expect(UTF::substr($str, -5) == 'יק לי', 'Substring at negative offset is correct', 'Substring at negative offset is wrong: ' . UTF::substr($str, -5)); $this->expect(UTF::strpos($str, 'ת וזה') == 20, 'String position detected accurately', 'String position detected incorrectly'); $this->expect(UTF::strpos($str, 'xyz') === FALSE, 'Non-existent substring returns FALSE', 'Non-existent substring returns non-boolean value: ' . UTF::strpos($str, 'xyz')); $this->expect(UTF::strrpos($str, 'כ') == 18, 'String position (right-to-left) detected accurately', 'String position (right-to-left) detected incorrectly'); $this->expect(UTF::strstr($str, 'לא מ', TRUE) == 'אני יכול לאכול זכוכית וזה ', 'Substring search returns correct value', 'Substring search failed: ' . UTF::strstr($str, 'לא מ', TRUE)); $this->expect(UTF::strstr($str, 'לא מזי') == 'לא מזיק לי', 'Substring search returns correct latent value', 'Substring search failed: ' . UTF::strstr($str, 'לא מ')); $this->expect(UTF::substr_count($str, 'כו') == 3, 'Substring count is correct', 'Substring count is incorrect'); echo $this->render('basic/results.htm'); }
/** * Translate emoji tokens to Unicode font-supported symbols * * The callback function receives two arguments: * The value to filter, and any parameters used in the filter rule. It should returned the filtered value. * * @param $value * @param array $param * * @return type * @link https://fatfreeframework.com/utf-unicode-string-manager#emojify */ public function filter_emojify($value, $param = null) { return \UTF::instance()->emojify($value); }