Commit 9fdbfc36 authored by Jamie Carl's avatar Jamie Carl 🤙🏼

Merge branch 'master' into 2.4-stable

parents b080cdf9 bec2146d
Pipeline #1983 passed with stage
in 5 seconds
......@@ -361,6 +361,8 @@
<Folder Include="docs\" />
<Folder Include="docs\advanced\databases\" />
<Folder Include="docs\advanced\view-helpers\" />
<Folder Include="docs\getting_started\" />
<Folder Include="docs\getting_started\web\" />
<Folder Include="docs\images\" />
<Folder Include="docs\intro\" />
<Folder Include="docs\advanced\caching\" />
......@@ -456,6 +458,12 @@
<Content Include="docs\basics\controllers.md" />
<Content Include="docs\basics\routing.md" />
<Content Include="docs\basics\layout.md" />
<Content Include="docs\getting_started\create_application.md" />
<Content Include="docs\getting_started\install.md" />
<Content Include="docs\getting_started\requirements.md" />
<Content Include="docs\getting_started\set_locale.md" />
<Content Include="docs\getting_started\web\apache.md" />
<Content Include="docs\getting_started\web\nginx.md" />
<Content Include="docs\images\mvc-overview.gif" />
<Content Include="docs\images\mvc_diagram.png" />
<Content Include="docs\advanced\authentication.md" />
......
# Configuration
## Includes
You can include another configuration if needed.
```
include = production
```
This is useful for if you have a development server or other stripped down server. You can set all your options up in the [production] configuration, include that config, and then only add the settings that you want to override. For example, you may have debugging off by default, so you could include the production config and then just set debug = true.
## Application Directives
These directives are used throughout the application and affect it's functionality only.
......@@ -49,24 +59,42 @@ app.errorController = "Error"
If this is true, the application will compress any output that it produces. This includes stylesheets and javascript files.
```
pp.compress = false
app.compress = false
```
### app.timer
Enables the built-in application execution timer which is available from inside a controller at
Enables the built-in application execution timer which is available from inside a controller at
```
$this->application->timer
```
This timer is used by the application to track performace during various stages of execution. Below is a list of available timers automatically
created by the application during execution. Any timers that are prefixed with an underscore (_) are pre-stage timers, meaning they hold the execution
time between the global start time and the start of the timer stage.
* **global** - The global timer is available in all Hazaar\Timer objects and is the time between when the Hazaar MVC application.php file is loaded (known as
the global start time) and now.
* **init** - The time taken to initialise the Hazaar\Application object. This includes everything that occurs in the Hazaar\Application constructor, such as
initialising the class loader, loading the configuration and setting up the application environment.
* **_bootstrap** - The time taken to get to the bootstrap stage from global start.
* **bootstrap** - The time taken to bootstrap the application. Bootstrapping the application involves all the steps required to prepare the target controller
for execution. This includes setting up the operating locale, determining the requested controller using the request path and any routing
aliases/scripts, as well as initialising the target controller so it is ready for execution.
* **_exec** - The time taken to get to the exec stage from global start.
* **exec** - The time taken to actually execute the application. At this point the target controller will be loaded and prepared so this is just the stage
where the controller performs it's tasks. This can include load/rendering/outputting views, generating and outputting JSON data, etc.
* **shutdown** - This timer is open ended and is started at the end of the exec stage. Theoretically the Hazaar\Application::shutdown() method should be
almost the last thing to execute so using the timer at that point can give an indication of any shutdown issues.
```
app.timer = true
```
### app.maxload
If this directive exists it will activate load average protection. When the application executes it will check the 1 minute load average and if it is greater than this number Hazaar will return a 503 Too Busy HTTP response.
If this directive exists it will activate load average protection. When the application executes it will check the 1 minute load average and if it is greater than this number the application will return a 503 Too Busy HTTP response.
```
app.maxload = 3.00
......@@ -114,7 +142,9 @@ The path, relative to the root/application path where models are kept. Usually '
paths.controller = "controllers"
```
## PHP Settings Directivesh3. php.*
## PHP Settings Directives
### php.*
These config options can be used to configure INI settings in PHP itself.
......@@ -153,16 +183,6 @@ Specifies the number of seconds of idle time that should pass before a session w
session.timeout = 180
```
## Includes
You can include another configuration if needed.
```
include = production
```
This is useful for if you have a development server or other stripped down server. You can set all your options up in the [production] configuration, include that config, and then only add the settings that you want to override. For example, you may have debugging off by default, so you could include the production config and then just set debug = true.
## View Directives
```
......@@ -174,4 +194,16 @@ This directive can be used to load one or more view helpers automatically, meani
```
view.helper.load[] = Bootstrap
view.helper.load[] = Google
```
\ No newline at end of file
```
```
view.cache
```
Enables internal caching of external view includes. This means that if an external JavaScript or CSS file is included using the
Hazaar\Controller::requires() or Hazaar\Controller::link() methods, then the file will be cached in the application runtime directory. This can be
used during development to reduce requests to external servers and allows for working "offline".
```
view.cache = true
```
......@@ -157,7 +157,7 @@ dataBinderValue.prototype.valueOf = function () {
return this.value;
};
dataBinderValue.prototype.set = function (value, label, other) {
dataBinderValue.prototype.set = function (value, label, other, update) {
value = this._parent.__nullify(value);
if (value !== null && typeof value === 'object'
|| value === this._value && label === this._label
......@@ -166,8 +166,10 @@ dataBinderValue.prototype.set = function (value, label, other) {
this._value = value;
this._label = label;
if (typeof other !== 'undefined') this._other = other;
this._parent._update(attr_name, true);
this._parent._trigger(this._name, this);
if (update !== false) {
this._parent._update(attr_name, true);
this._parent._trigger(this._name, this);
}
return this;
};
......@@ -177,8 +179,8 @@ dataBinderValue.prototype.save = function (no_label) {
return this.value;
};
dataBinderValue.prototype.empty = function () {
return this.set(null, null, null);
dataBinderValue.prototype.empty = function (update) {
return this.set(null, null, null, update);
};
dataBinderValue.prototype.update = function () {
......@@ -265,7 +267,7 @@ dataBinder.prototype._defineProperty = function (trigger_name, key) {
this._trigger(key, value);
if (attr instanceof dataBinder && value instanceof dataBinder) {
value._parent = this;
value._watchers = attr._watchers;
value._copy_watchers(attr);
value._trigger_diff(attr);
}
},
......@@ -282,6 +284,14 @@ dataBinder.prototype._defineProperty = function (trigger_name, key) {
});
};
dataBinder.prototype._copy_watchers = function (source) {
this._watchers = source._watchers;
for (x in source._attributes) {
if (source._attributes[x] instanceof dataBinder && x in this._attributes && this._attributes[x] instanceof dataBinder)
this._attributes[x]._copy_watchers(source._attributes[x]);
}
};
dataBinder.prototype.remove = function (key) {
if (!(key in this._attributes))
return;
......@@ -333,11 +343,12 @@ dataBinder.prototype._trigger = function (key, value) {
dataBinder.prototype._trigger_diff = function (source) {
if (!source instanceof dataBinder) return;
for (let x in this._attributes) {
if ((this._attributes[x] instanceof dataBinderValue ? this._attributes[x].value : this._attributes[x])
if (this._attributes[x] instanceof dataBinder) this._attributes[x]._trigger_diff(source[x])
else if ((this._attributes[x] instanceof dataBinderValue ? this._attributes[x].value : this._attributes[x])
!== (source[x] instanceof dataBinderValue ? source[x].value : source[x])) {
this._update(this._attr_name(x), true);
this._trigger(x, this._attributes[x]);
} else if (this._attributes[x] instanceof dataBinder) this._attributes[x]._trigger_diff(source[x]);
}
}
};
......@@ -395,6 +406,14 @@ dataBinder.prototype.unwatch = function (key, id) {
} else delete this._watchers[key];
};
dataBinder.prototype.unwatchAll = function () {
this._watchers = {};
for (let x in this._attributes) {
if (this._attributes[x] instanceof dataBinder)
this._attributes[x].unwatchAll();
}
};
dataBinder.prototype.keys = function () {
return Object.keys(this._attributes);
};
......
......@@ -220,33 +220,33 @@ class Application {
}
/*
* Create a timer for performance measuring
* Use the config to add search paths to the loader
*/
if($this->config->app->has('timer') && $this->config->app['timer'] == TRUE) {
$this->loader->addSearchPaths($this->config->get('paths'));
$this->timer = new Timer();
if(!defined('RUNTIME_PATH')){
$this->timer->start('init', HAZAAR_INIT_START);
define('RUNTIME_PATH', $this->runtimePath(null, true));
$this->timer->stop('init');
$this->GLOBALS['runtime'] = RUNTIME_PATH;
}
$this->request = Application\Request\Loader::load($this->config);
/*
* Use the config to add search paths to the loader
* Create a timer for performance measuring
*/
$this->loader->addSearchPaths($this->config->get('paths'));
if($this->config->app->has('timer') && $this->config->app['timer'] == TRUE) {
if(!defined('RUNTIME_PATH')){
$this->timer = new Timer();
define('RUNTIME_PATH', $this->runtimePath(null, true));
$this->timer->start('init', HAZAAR_INIT_START);
$this->GLOBALS['runtime'] = RUNTIME_PATH;
$this->timer->stop('init');
}
$this->request = Application\Request\Loader::load($this->config);
}
/**
......@@ -320,7 +320,7 @@ class Application {
// Try and create the directory automatically
try {
mkdir($path, 0775);
@mkdir($path, 0775);
}
catch(\Exception $e) {
......@@ -435,11 +435,11 @@ class Application {
if($this->timer) {
$this->timer->start('pre_boot', HAZAAR_EXEC_START);
$this->timer->start('_bootstrap', HAZAAR_EXEC_START);
$this->timer->stop('pre_boot');
$this->timer->stop('_bootstrap');
$this->timer->start('boot');
$this->timer->start('bootstrap');
}
......@@ -531,7 +531,7 @@ class Application {
}
if($this->timer)
$this->timer->stop('boot');
$this->timer->stop('bootstrap');
return $this;
......@@ -558,9 +558,16 @@ class Application {
*/
public function run(Controller $controller = NULL) {
if($this->timer)
if($this->timer){
$this->timer->start('_exec', HAZAAR_EXEC_START);
$this->timer->stop('_exec');
$this->timer->start('exec');
}
if(!$controller)
$controller = $this->controller;
......@@ -620,7 +627,7 @@ class Application {
if($this->timer) {
$this->timer->start('post_exec');
$this->timer->start('shutdown');
$this->timer->stop('exec');
......
......@@ -106,17 +106,8 @@ class Url {
}
if($m_params) {
foreach(explode('&', $m_params) as $param) {
list($key, $value) = explode('=', $param);
$this->params[$key] = $value;
}
}
if($m_params)
parse_str($m_params, $this->params);
/*
* Sanitize the controller/method
......
......@@ -209,14 +209,14 @@ class ViewRenderer extends \Hazaar\Controller\Action\Helper {
$view->setRequiresParam($this->_requires_param);
foreach($this->_requires as $req)
$view->requires($req);
$view->requires($req[0], $req[1], $req[2]);
}
if(is_array($this->_links)){
foreach($this->_links as $link)
$view->link($link[0], $link[1]);
$view->link($link[0], $link[1], $link[2]);
}
......@@ -237,21 +237,21 @@ class ViewRenderer extends \Hazaar\Controller\Action\Helper {
}
public function requires($script) {
public function requires($script, $charset = NULL, $cache_local = null) {
if(! method_exists($this->view, 'requires'))
throw new \Exception('The current view does not support script imports');
$this->_requires[] = $script;
$this->_requires[] = array($script, $charset, $cache_local);
}
public function link($href, $rel = NULL) {
public function link($href, $rel = NULL, $cache_local = null) {
if(! method_exists($this->view, 'link'))
throw new \Exception('The current view does not support HTML links');
$this->_links[] = array($href, $rel);
$this->_links[] = array($href, $rel, $cache_local);
}
......
......@@ -197,8 +197,8 @@ abstract class Response implements Response\_Interface {
http_response_code($this->status_code);
if($this->content_type)
header('Content-Type: ' . $this->getContentType());
if($content_type = $this->getContentType())
header('Content-Type: ' . $content_type);
if($content_length === null)
$content_length = $this->getContentLength();
......
......@@ -164,7 +164,7 @@ class File extends \Hazaar\Controller\Response\HTTP\OK {
public function getContentType() {
return $this->file->mime_content_type();
return $this->content_type ? $this->content_type : $this->file->mime_content_type();
}
......
......@@ -199,6 +199,9 @@ class File {
if($this->contents)
return strlen($this->contents);
if(!$this->exists())
return false;
return $this->backend->filesize($this->source_file);
}
......@@ -220,6 +223,9 @@ class File {
public function is_readable() {
if(!$this->exists())
return false;
return $this->backend->is_readable($this->source_file);
}
......@@ -232,6 +238,9 @@ class File {
public function is_dir() {
if(!$this->exists())
return false;
return $this->backend->is_dir($this->source_file);
}
......@@ -247,12 +256,18 @@ class File {
public function is_link() {
if(!$this->exists())
return false;
return $this->backend->is_link($this->source_file);
}
public function is_file() {
if(!$this->exists())
return false;
return $this->backend->is_file($this->source_file);
}
......@@ -265,30 +280,48 @@ class File {
public function type() {
if(!$this->exists())
return false;
return $this->backend->filetype($this->source_file);
}
public function ctime() {
if(!$this->exists())
return false;
return $this->backend->filectime($this->source_file);
}
public function mtime() {
if(!$this->exists())
return false;
return $this->backend->filemtime($this->source_file);
}
public function atime() {
if(!$this->exists())
return false;
return $this->backend->fileatime($this->source_file);
}
public function has_contents() {
if($this->contents)
return true;
if(!$this->exists())
return false;
return ($this->backend->filesize($this->source_file) > 0);
}
......
This diff is collapsed.
......@@ -17,7 +17,7 @@ class Dir {
function __construct($path, Backend\_Interface $backend = NULL, Manager $manager = null) {
if(! $backend)
$backend = new Backend\Local(array('root' => '/'));
$backend = new Backend\Local(array('root' => ((substr(PHP_OS, 0, 3) == 'WIN') ? substr(APPLICATION_PATH, 0, 3) : '/')));
$this->backend = $backend;
......
......@@ -27,23 +27,23 @@
function ake($array, $key, $default = NULL, $non_empty = FALSE) {
if(is_string($key) || is_int($key)){
if ((is_array($array) || $array instanceof \ArrayAccess)
&& isset($array[$key])
&& $array[$key] !== NULL
&& (!$non_empty || ($non_empty && (is_string($array[$key]) ? trim($array[$key]) : $array[$key]))))
return $array[$key];
if ($array instanceof \Hazaar\Model\Strict)
return $array->ake($key, $default, $non_empty);
if(is_object($array)
&& property_exists($array, $key)
&& (!$non_empty || ($non_empty && trim($array->$key) !== NULL)))
return $array->$key;
}
return $default;
}
......@@ -1482,13 +1482,13 @@ function recursive_iterator_to_array(\Traversable $it) {
* in the comparison. Also, unlike the PHP array_diff_assoc() function, this function recurse into child arrays.
*
* @param array $array1 The array to compare from.
*
*
* @param array $array2 The array to compare against.
*
*
* @param array ... More arrays to compare against.
*
*
* @return array
*
*
* @author Diego Dias <diego.dias@apir.com.au>
*
* @since 2.4.1
......@@ -1499,46 +1499,56 @@ function array_diff_assoc_recursive() {
$array1 = array_shift($arrays);
$difference = array();
$diff = array();
foreach($arrays as $array_compare){
foreach($array1 as $key => $value) {
foreach($array1 as $key => $value) {
foreach($arrays as $array_compare){
if(is_array($value)) {
//Check if the value exists in the compare array and if not, check the next array
if((is_array($array_compare) && !array_key_exists($key, $array_compare))
|| ($array_compare instanceof \stdClass && !property_exists($array_compare, $key)))
continue;
if(!isset($array_compare[$key]) || !is_array($array_compare[$key])){
if(!(is_array($value) || $value instanceof \stdClass) && $value !== ake($array_compare, $key))
continue;
$difference[$key] = $value;
if(is_array($value) || $value instanceof \stdClass){
}else{
$compare_value = ake($array_compare, $key);
$new_diff = array_diff_assoc_recursive($value, $array_compare[$key]);
if(!(is_array($compare_value) || $compare_value instanceof \stdClass))
break;
if(!empty($new_diff))
$difference[$key] = $new_diff;
$child_diff = array_diff_assoc_recursive($value, $compare_value);
}
if(!empty($child_diff)){
}elseif(!array_key_exists($key, $array_compare) || $array_compare[$key] !== $value){
$value = $child_diff;
$difference[$key] = $value;
break;
}
}
continue 2;
}
$diff[$key] = $value;
}
return $difference;
return $diff;
}
/**
* Recursively convert an object into an array.
*
*
* This is basically a recursive version of PHP's get_object_vars().
*
*
* @param object $object The object to convert.
* @return array|boolean Returns the converted object as an array or false on failure.
*/
......@@ -1558,4 +1568,36 @@ function object_to_array($object){
return $array;
}
\ No newline at end of file
}
/**
* Searches the array using a callback function and returns the first corresponding key if successful.
*
* @param mixed $haystack The array.
* @param callable $callback The callback function to use. This function should return true if the value matches.
* @return mixed
*/
function array_usearch($haystack, callable $callback){
foreach($haystack as $key => $value){
if($callback($value, $key) === true)
return $key;
}
return false;
}
/**
* Checks if a value exists in an array using a callback function.
*
* @param mixed $haystack The array.
* @param callable $callback The callback function to use. This function should return true if the value matches.
* @return boolean True if the value is found in the array, false otherwise.
*/
function in_uarray($haystack, callable $callback){
return array_usearch($haystack, $callback) !== false;
}
......@@ -118,10 +118,14 @@ class DataBinderValue implements \JsonSerializable {
foreach($object as $key => $value){
if((is_array($value) && array_key_exists('__hz_value', $value)) || ($value instanceof \stdClass && property_exists($value, '__hz_value')))
$value = (DataBinderValue::create($value))->value;
elseif($recursive === true)
$value = DataBinderValue::resolve($value);
if(is_array($value) || $value instanceof \stdClass){
if((is_array($value) && array_key_exists('__hz_value', $value)) || ($value instanceof \stdClass && property_exists($value, '__hz_value')))
$value = (DataBinderValue::create($value))->value;
elseif($recursive === true)
$value = DataBinderValue::resolve($value);
}
$array[$key] = $value;
......
......@@ -32,9 +32,22 @@ abstract class DataTypeConverter {
protected static $type_aliases = array(
'bool' => 'boolean',
'number' => 'float',
'text' => 'string'
'text' => 'string',
'date' => 'Hazaar\Date'
);
/**
* Convert a variable to the request type.
*
* This also allows us to convert complex types, such as arrays, into objects.
*
* @param mixed $value The value to convert.
* @param mixed $type The type to convert it to.
*
* @throws Exception\InvalidDataType
* @throws \Exception
* @return mixed
*/
protected static function convertType(&$value, $type) {
if($value === null || $type === null) return $value;
......@@ -140,6 +153,10 @@ abstract class DataTypeConverter {
}
}else{
throw new \Exception("Unable to convert value to unknown type or class '$type'.");
}
return $value;
......
......@@ -311,6 +311,10 @@ abstract class Strict extends DataTypeConverter implements \ArrayAccess, \Iterat
$value = $value->get($part,