diff -Nru php-nette-2.3.10/client-side/netteForms.js php-nette-2.4-20160731/client-side/netteForms.js
--- php-nette-2.3.10/client-side/netteForms.js 2016-04-13 18:50:54.000000000 +0000
+++ php-nette-2.4-20160731/client-side/netteForms.js 2016-07-31 17:46:56.000000000 +0000
@@ -6,6 +6,10 @@
*/
(function(global, factory) {
+ if (!global.JSON) {
+ return;
+ }
+
if (typeof define === 'function' && define.amd) {
define(function() {
return factory(global);
@@ -13,8 +17,11 @@
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(global);
} else {
+ var init = !global.Nette || !global.Nette.noInit;
global.Nette = factory(global);
- global.Nette.initOnLoad();
+ if (init) {
+ global.Nette.initOnLoad();
+ }
}
}(typeof window !== 'undefined' ? window : this, function(window) {
@@ -23,20 +30,35 @@
var Nette = {};
+Nette.formErrors = [];
+Nette.version = '2.4';
+
+
/**
* Attaches a handler to an event for the element.
*/
Nette.addEvent = function(element, on, callback) {
- var original = element['on' + on];
- element['on' + on] = function() {
- if (typeof original === 'function' && original.apply(element, arguments) === false) {
- return false;
- }
- return callback.apply(element, arguments);
- };
+ if (element.addEventListener) {
+ element.addEventListener(on, callback);
+ } else if (on === 'DOMContentLoaded') {
+ element.attachEvent('onreadystatechange', function() {
+ if (element.readyState === 'complete') {
+ callback.call(this);
+ }
+ });
+ } else {
+ element.attachEvent('on' + on, getHandler(callback));
+ }
};
+function getHandler(callback) {
+ return function(e) {
+ return callback.call(this, e);
+ };
+}
+
+
/**
* Returns the value of form element.
*/
@@ -116,7 +138,7 @@
/**
* Validates form element against given rules.
*/
-Nette.validateControl = function(elem, rules, onlyCheck, value) {
+Nette.validateControl = function(elem, rules, onlyCheck, value, emptyOptional) {
elem = elem.tagName ? elem : elem[0]; // RadioNodeList
rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules'));
value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value;
@@ -126,15 +148,20 @@
op = rule.op.match(/(~)?([^?]+)/),
curElem = rule.control ? elem.form.elements.namedItem(rule.control) : elem;
+ rule.neg = op[1];
+ rule.op = op[2];
+ rule.condition = !!rule.rules;
+
if (!curElem) {
continue;
+ } else if (rule.op === 'optional') {
+ emptyOptional = !Nette.validateRule(elem, ':filled', null, value);
+ continue;
+ } else if (emptyOptional && !rule.condition && rule.op !== ':filled') {
+ return true;
}
- rule.neg = op[1];
- rule.op = op[2];
- rule.condition = !!rule.rules;
curElem = curElem.tagName ? curElem : curElem[0]; // RadioNodeList
-
var curValue = elem === curElem ? value : {value: Nette.getEffectiveValue(curElem)},
success = Nette.validateRule(curElem, rule.op, rule.arg, curValue);
@@ -145,7 +172,7 @@
}
if (rule.condition && success) {
- if (!Nette.validateControl(elem, rule.rules, onlyCheck, value)) {
+ if (!Nette.validateControl(elem, rule.rules, onlyCheck, value, rule.op === ':blank' ? false : emptyOptional)) {
return false;
}
} else if (!rule.condition && !success) {
@@ -162,6 +189,12 @@
return false;
}
}
+
+ if (!onlyCheck && elem.type === 'number' && !elem.validity.valid) {
+ Nette.addError(elem, 'Please enter a valid value.');
+ return false;
+ }
+
return true;
};
@@ -173,11 +206,14 @@
var form = sender.form || sender,
scope = false;
+ Nette.formErrors = [];
+
if (form['nette-submittedBy'] && form['nette-submittedBy'].getAttribute('formnovalidate') !== null) {
var scopeArr = Nette.parseJSON(form['nette-submittedBy'].getAttribute('data-nette-validation-scope'));
if (scopeArr.length) {
scope = new RegExp('^(' + scopeArr.join('-|') + '-)');
} else {
+ Nette.showFormErrors(form, []);
return true;
}
}
@@ -201,11 +237,13 @@
continue;
}
- if (!Nette.validateControl(elem)) {
+ if (!Nette.validateControl(elem) && !Nette.formErrors.length) {
return false;
}
}
- return true;
+ var success = !Nette.formErrors.length;
+ Nette.showFormErrors(form, Nette.formErrors);
+ return success;
};
@@ -226,14 +264,42 @@
/**
- * Display error message.
+ * Adds error message to the queue.
*/
Nette.addError = function(elem, message) {
- if (message) {
- alert(message);
+ Nette.formErrors.push({
+ element: elem,
+ message: message
+ });
+};
+
+
+/**
+ * Display error messages.
+ */
+Nette.showFormErrors = function(form, errors) {
+ var messages = [],
+ focusElem;
+
+ for (var i in errors) {
+ var elem = errors[i].element,
+ message = errors[i].message;
+
+ if (!Nette.inArray(messages, message)) {
+ messages.push(message);
+
+ if (!focusElem && elem.focus) {
+ focusElem = elem;
+ }
+ }
}
- if (elem.focus) {
- elem.focus();
+
+ if (messages.length) {
+ alert(messages.join('\n'));
+
+ if (focusElem) {
+ focusElem.focus();
+ }
}
};
@@ -243,7 +309,10 @@
*/
Nette.expandRuleArgument = function(form, arg) {
if (arg && arg.control) {
- arg = Nette.getEffectiveValue(form.elements.namedItem(arg.control));
+ var control = form.elements.namedItem(arg.control),
+ value = {value: Nette.getEffectiveValue(control)};
+ Nette.validateControl(control, null, true, value);
+ arg = value.value;
}
return arg;
};
@@ -273,6 +342,9 @@
Nette.validators = {
filled: function(elem, arg, val) {
+ if (elem.type === 'number' && elem.validity.badInput) {
+ return true;
+ }
return val !== '' && val !== false && val !== null
&& (!Nette.isArray(val) || !!val.length)
&& (!window.FileList || !(val instanceof window.FileList) || val.length);
@@ -318,14 +390,35 @@
},
minLength: function(elem, arg, val) {
+ if (elem.type === 'number') {
+ if (elem.validity.tooShort) {
+ return false
+ } else if (elem.validity.badInput) {
+ return null;
+ }
+ }
return val.length >= arg;
},
maxLength: function(elem, arg, val) {
+ if (elem.type === 'number') {
+ if (elem.validity.tooLong) {
+ return false
+ } else if (elem.validity.badInput) {
+ return null;
+ }
+ }
return val.length <= arg;
},
length: function(elem, arg, val) {
+ if (elem.type === 'number') {
+ if (elem.validity.tooShort || elem.validity.tooLong) {
+ return false
+ } else if (elem.validity.badInput) {
+ return null;
+ }
+ }
arg = Nette.isArray(arg) ? arg : [arg, arg];
return (arg[0] === null || val.length >= arg[0]) && (arg[1] === null || val.length <= arg[1]);
},
@@ -359,10 +452,16 @@
},
integer: function(elem, arg, val) {
+ if (elem.type === 'number' && elem.validity.badInput) {
+ return false;
+ }
return (/^-?[0-9]+$/).test(val);
},
'float': function(elem, arg, val, value) {
+ if (elem.type === 'number' && elem.validity.badInput) {
+ return false;
+ }
val = val.replace(' ', '').replace(',', '.');
if ((/^-?[0-9]*[.,]?[0-9]+$/).test(val)) {
value.value = val;
@@ -372,14 +471,35 @@
},
min: function(elem, arg, val) {
+ if (elem.type === 'number') {
+ if (elem.validity.rangeUnderflow) {
+ return false
+ } else if (elem.validity.badInput) {
+ return null;
+ }
+ }
return Nette.validators.range(elem, [arg, null], val);
},
max: function(elem, arg, val) {
+ if (elem.type === 'number') {
+ if (elem.validity.rangeOverflow) {
+ return false
+ } else if (elem.validity.badInput) {
+ return null;
+ }
+ }
return Nette.validators.range(elem, [null, arg], val);
},
range: function(elem, arg, val) {
+ if (elem.type === 'number') {
+ if (elem.validity.rangeUnderflow || elem.validity.rangeOverflow) {
+ return false
+ } else if (elem.validity.badInput) {
+ return null;
+ }
+ }
return Nette.isArray(arg) ?
((arg[0] === null || parseFloat(val) >= arg[0]) && (arg[1] === null || parseFloat(val) <= arg[1])) : null;
},
@@ -497,11 +617,9 @@
Nette.parseJSON = function(s) {
- s = s || '[]';
- if (s.substr(0, 3) === '{op') {
- return eval('[' + s + ']'); // backward compatibility
- }
- return window.JSON && window.JSON.parse ? JSON.parse(s) : eval(s);
+ return (s || '').substr(0, 3) === '{op'
+ ? eval('[' + s + ']') // backward compatibility with Nette 2.0.x
+ : JSON.parse(s || '[]');
};
@@ -526,19 +644,14 @@
if (!Nette.validateForm(form)) {
if (e && e.stopPropagation) {
e.stopPropagation();
+ e.preventDefault();
} else if (window.event) {
event.cancelBubble = true;
+ event.returnValue = false;
}
- return false;
}
});
- Nette.addEvent(form, 'click', function(e) {
- e = e || event;
- var target = e.target || e.srcElement;
- form['nette-submittedBy'] = (target.type in {submit: 1, image: 1}) ? target : null;
- });
-
Nette.toggleForm(form);
};
@@ -547,7 +660,7 @@
* @private
*/
Nette.initOnLoad = function() {
- Nette.addEvent(window, 'load', function() {
+ Nette.addEvent(document, 'DOMContentLoaded', function() {
for (var i = 0; i < document.forms.length; i++) {
var form = document.forms[i];
for (var j = 0; j < form.elements.length; j++) {
@@ -557,6 +670,13 @@
}
}
}
+
+ Nette.addEvent(document.body, 'click', function(e) {
+ var target = e.target || e.srcElement;
+ if (target.form && target.type in {submit: 1, image: 1}) {
+ target.form['nette-submittedBy'] = target;
+ }
+ });
});
};
diff -Nru php-nette-2.3.10/debian/changelog php-nette-2.4-20160731/debian/changelog
--- php-nette-2.3.10/debian/changelog 2016-04-25 20:47:23.000000000 +0000
+++ php-nette-2.4-20160731/debian/changelog 2016-09-23 07:37:58.000000000 +0000
@@ -1,3 +1,20 @@
+php-nette (2.4-20160731-1) unstable; urgency=medium
+
+ * Update debian/watch to accommodate the new date-based ZIP releases
+ * Import Upstream version v2.4.0 (20160731)
+ * Update upstream-changelog
+ * Refresh 0001-Fix-path-in-examples.patch
+ * Remove deleted files from debian/copyright
+ * Add uncompressed sources to debian/missing-sources (from Tracy Github repo)
+
+ -- Florian Schlichting Nette Forms live validation example
+
+
+
+
diff -Nru php-nette-2.3.10/examples/Forms/localization.php php-nette-2.4-20160731/examples/Forms/localization.php
--- php-nette-2.3.10/examples/Forms/localization.php 2016-04-13 18:50:46.000000000 +0000
+++ php-nette-2.4-20160731/examples/Forms/localization.php 2016-07-31 17:46:44.000000000 +0000
@@ -48,16 +48,16 @@
$form->addText('age', 'Your age:')
->setRequired('Enter your age')
->addRule($form::INTEGER, 'Age must be numeric value')
- ->addRule($form::RANGE, 'Age must be in range from %d to %d', array(10, 100));
+ ->addRule($form::RANGE, 'Age must be in range from %d to %d', [10, 100]);
-$countries = array(
- 'World' => array(
+$countries = [
+ 'World' => [
'bu' => 'Buranda',
'qu' => 'Qumran',
'st' => 'Saint Georges Island',
- ),
+ ],
'?' => 'other',
-);
+];
$form->addSelect('country', 'Country:', $countries)
->setPrompt('Select your country');
diff -Nru php-nette-2.3.10/examples/Forms/manual-rendering.php php-nette-2.4-20160731/examples/Forms/manual-rendering.php
--- php-nette-2.3.10/examples/Forms/manual-rendering.php 2016-04-13 18:50:46.000000000 +0000
+++ php-nette-2.4-20160731/examples/Forms/manual-rendering.php 2016-07-31 17:46:44.000000000 +0000
@@ -23,14 +23,14 @@
$form->addText('age')
->setRequired('Enter your age');
-$form->addRadioList('gender', NULL, array(
+$form->addRadioList('gender', NULL, [
'm' => 'male',
'f' => 'female',
-));
+]);
$form->addText('email')
- ->addCondition($form::FILLED)
- ->addRule($form::EMAIL, 'Incorrect email address');
+ ->setRequired(FALSE)
+ ->addRule($form::EMAIL, 'Incorrect email address');
$form->addSubmit('submit');
diff -Nru php-nette-2.3.10/examples/Micro-blog/composer.json php-nette-2.4-20160731/examples/Micro-blog/composer.json
--- php-nette-2.3.10/examples/Micro-blog/composer.json 2016-04-13 18:50:22.000000000 +0000
+++ php-nette-2.4-20160731/examples/Micro-blog/composer.json 2016-07-31 17:46:20.000000000 +0000
@@ -14,9 +14,15 @@
}
],
"require": {
- "php": ">=5.3.0",
- "nette/nette": "~2.3.0"
+ "php": ">=5.4",
+ "nette/application": "~2.3",
+ "nette/bootstrap": "~2.3",
+ "nette/database": "~2.3",
+ "nette/robot-loader": "~2.3",
+ "latte/latte": "~2.3",
+ "tracy/tracy": "~2.3"
},
+ "minimum-stability": "dev",
"config": {
"vendor-dir": "www/data/vendor"
}
diff -Nru php-nette-2.3.10/examples/Micro-blog/www/data/TemplateRouter.php php-nette-2.4-20160731/examples/Micro-blog/www/data/TemplateRouter.php
--- php-nette-2.3.10/examples/Micro-blog/www/data/TemplateRouter.php 2016-04-13 18:50:22.000000000 +0000
+++ php-nette-2.4-20160731/examples/Micro-blog/www/data/TemplateRouter.php 2016-07-31 17:46:20.000000000 +0000
@@ -24,7 +24,7 @@
$latte = new Latte\Engine;
$latte->setTempDirectory($cachePath . '/cache');
$macroSet = new Latte\Macros\MacroSet($latte->getCompiler());
- $macroSet->addMacro('url', function () {}); // ignore
+ $macroSet->addMacro('url', function () {}, NULL, NULL, $macroSet::ALLOWED_IN_HEAD); // ignore
return $latte;
})->setFile($file);
});
@@ -34,12 +34,12 @@
public function scanRoutes($path)
{
- $routes = array();
+ $routes = [];
$latte = new Latte\Engine;
$macroSet = new Latte\Macros\MacroSet($latte->getCompiler());
$macroSet->addMacro('url', function ($node) use (&$routes, &$file) {
$routes[$node->args] = (string) $file;
- });
+ }, NULL, NULL, $macroSet::ALLOWED_IN_HEAD);
foreach (Nette\Utils\Finder::findFiles('*.latte')->from($path) as $file) {
$latte->compile($file);
}
diff -Nru php-nette-2.3.10/examples/Micro-blog/www/data/templates/@layout.latte php-nette-2.4-20160731/examples/Micro-blog/www/data/templates/@layout.latte
--- php-nette-2.3.10/examples/Micro-blog/www/data/templates/@layout.latte 2016-04-13 18:50:22.000000000 +0000
+++ php-nette-2.4-20160731/examples/Micro-blog/www/data/templates/@layout.latte 2016-07-31 17:46:20.000000000 +0000
@@ -2,7 +2,7 @@
-
' + . '', + ]; } }); @@ -53,13 +54,13 @@ if ($e instanceof Nette\Neon\Exception && preg_match('#line (\d+)#', $e->getMessage(), $m) && ($trace = Helpers::findTrace($e->getTrace(), 'Nette\Neon\Decoder::decode')) ) { - return array( + return [ 'tab' => 'NEON', 'panel' => ($trace2 = Helpers::findTrace($e->getTrace(), 'Nette\DI\Config\Adapters\NeonAdapter::load')) ? '' . BlueScreen::highlightLine(htmlspecialchars($e->sourceCode, ENT_IGNORE, 'UTF-8'), $e->sourceLine) - . '', - ); + . 'File: ' . Helpers::editorLink($trace2['args'][0], $m[1]) . '
' . BlueScreen::highlightFile($trace2['args'][0], $m[1]) : BlueScreen::highlightPhp($trace['args'][0], $m[1]), - ); + ]; } }); } diff -Nru php-nette-2.3.10/Nette/Bridges/HttpDI/HttpExtension.php php-nette-2.4-20160731/Nette/Bridges/HttpDI/HttpExtension.php --- php-nette-2.3.10/Nette/Bridges/HttpDI/HttpExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/HttpDI/HttpExtension.php 2016-07-31 17:46:36.000000000 +0000 @@ -15,47 +15,57 @@ */ class HttpExtension extends Nette\DI\CompilerExtension { - public $defaults = array( - 'proxy' => array(), - 'headers' => array( + public $defaults = [ + 'proxy' => [], + 'headers' => [ 'X-Powered-By' => 'Nette Framework', 'Content-Type' => 'text/html; charset=utf-8', - ), + ], 'frames' => 'SAMEORIGIN', // X-Frame-Options - ); + ]; + + /** @var bool */ + private $cliMode; + + + public function __construct($cliMode = FALSE) + { + $this->cliMode = $cliMode; + } public function loadConfiguration() { - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); $config = $this->validateConfig($this->defaults); - $container->addDefinition($this->prefix('requestFactory')) - ->setClass('Nette\Http\RequestFactory') - ->addSetup('setProxy', array($config['proxy'])); + $builder->addDefinition($this->prefix('requestFactory')) + ->setClass(Nette\Http\RequestFactory::class) + ->addSetup('setProxy', [$config['proxy']]); - $container->addDefinition($this->prefix('request')) - ->setClass('Nette\Http\Request') + $builder->addDefinition($this->prefix('request')) + ->setClass(Nette\Http\Request::class) ->setFactory('@Nette\Http\RequestFactory::createHttpRequest'); - $container->addDefinition($this->prefix('response')) - ->setClass('Nette\Http\Response'); + $builder->addDefinition($this->prefix('response')) + ->setClass(Nette\Http\Response::class); - $container->addDefinition($this->prefix('context')) - ->setClass('Nette\Http\Context'); + $builder->addDefinition($this->prefix('context')) + ->setClass(Nette\Http\Context::class) + ->addSetup('::trigger_error', ['Service http.context is deprecated.', E_USER_DEPRECATED]); if ($this->name === 'http') { - $container->addAlias('nette.httpRequestFactory', $this->prefix('requestFactory')); - $container->addAlias('nette.httpContext', $this->prefix('context')); - $container->addAlias('httpRequest', $this->prefix('request')); - $container->addAlias('httpResponse', $this->prefix('response')); + $builder->addAlias('nette.httpRequestFactory', $this->prefix('requestFactory')); + $builder->addAlias('nette.httpContext', $this->prefix('context')); + $builder->addAlias('httpRequest', $this->prefix('request')); + $builder->addAlias('httpResponse', $this->prefix('response')); } } public function afterCompile(Nette\PhpGenerator\ClassType $class) { - if (PHP_SAPI === 'cli') { + if ($this->cliMode) { return; } @@ -69,12 +79,12 @@ } elseif (preg_match('#^https?:#', $frames)) { $frames = "ALLOW-FROM $frames"; } - $initialize->addBody('header(?);', array("X-Frame-Options: $frames")); + $initialize->addBody('header(?);', ["X-Frame-Options: $frames"]); } foreach ($config['headers'] as $key => $value) { if ($value != NULL) { // intentionally == - $initialize->addBody('header(?);', array("$key: $value")); + $initialize->addBody('header(?);', ["$key: $value"]); } } } diff -Nru php-nette-2.3.10/Nette/Bridges/HttpDI/SessionExtension.php php-nette-2.4-20160731/Nette/Bridges/HttpDI/SessionExtension.php --- php-nette-2.3.10/Nette/Bridges/HttpDI/SessionExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/HttpDI/SessionExtension.php 2016-07-31 17:46:36.000000000 +0000 @@ -15,55 +15,59 @@ */ class SessionExtension extends Nette\DI\CompilerExtension { - public $defaults = array( + public $defaults = [ 'debugger' => FALSE, 'autoStart' => 'smart', // true|false|smart 'expiration' => NULL, - ); + ]; /** @var bool */ private $debugMode; + /** @var bool */ + private $cliMode; + - public function __construct($debugMode = FALSE) + public function __construct($debugMode = FALSE, $cliMode = FALSE) { $this->debugMode = $debugMode; + $this->cliMode = $cliMode; } public function loadConfiguration() { - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); $config = $this->getConfig() + $this->defaults; $this->setConfig($config); - $session = $container->addDefinition($this->prefix('session')) - ->setClass('Nette\Http\Session'); + $session = $builder->addDefinition($this->prefix('session')) + ->setClass(Nette\Http\Session::class); if ($config['expiration']) { - $session->addSetup('setExpiration', array($config['expiration'])); + $session->addSetup('setExpiration', [$config['expiration']]); } if ($this->debugMode && $config['debugger']) { - $session->addSetup('@Tracy\Bar::addPanel', array( - new Nette\DI\Statement('Nette\Bridges\HttpTracy\SessionPanel'), - )); + $session->addSetup('@Tracy\Bar::addPanel', [ + new Nette\DI\Statement(Nette\Bridges\HttpTracy\SessionPanel::class) + ]); } unset($config['expiration'], $config['autoStart'], $config['debugger']); if (!empty($config)) { - $session->addSetup('setOptions', array($config)); + $session->addSetup('setOptions', [$config]); } if ($this->name === 'session') { - $container->addAlias('session', $this->prefix('session')); + $builder->addAlias('session', $this->prefix('session')); } } public function afterCompile(Nette\PhpGenerator\ClassType $class) { - if (PHP_SAPI === 'cli') { + if ($this->cliMode) { return; } @@ -72,10 +76,10 @@ $name = $this->prefix('session'); if ($config['autoStart'] === 'smart') { - $initialize->addBody('$this->getService(?)->exists() && $this->getService(?)->start();', array($name, $name)); + $initialize->addBody('$this->getService(?)->exists() && $this->getService(?)->start();', [$name, $name]); } elseif ($config['autoStart']) { - $initialize->addBody('$this->getService(?)->start();', array($name)); + $initialize->addBody('$this->getService(?)->start();', [$name]); } } diff -Nru php-nette-2.3.10/Nette/Bridges/HttpTracy/SessionPanel.php php-nette-2.4-20160731/Nette/Bridges/HttpTracy/SessionPanel.php --- php-nette-2.3.10/Nette/Bridges/HttpTracy/SessionPanel.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/HttpTracy/SessionPanel.php 2016-07-31 17:46:36.000000000 +0000 @@ -14,8 +14,9 @@ /** * Session panel for Debugger Bar. */ -class SessionPanel extends Nette\Object implements Tracy\IBarPanel +class SessionPanel implements Tracy\IBarPanel { + use Nette\SmartObject; /** * Renders tab. diff -Nru php-nette-2.3.10/Nette/Bridges/HttpTracy/templates/SessionPanel.panel.phtml php-nette-2.4-20160731/Nette/Bridges/HttpTracy/templates/SessionPanel.panel.phtml --- php-nette-2.3.10/Nette/Bridges/HttpTracy/templates/SessionPanel.panel.phtml 2016-04-13 18:50:56.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/HttpTracy/templates/SessionPanel.panel.phtml 2016-07-31 17:46:58.000000000 +0000 @@ -9,7 +9,7 @@-- - - - - - -Session # (Lifetime: )
+Session #= htmlspecialchars(session_id(), ENT_IGNORE, 'UTF-8') ?> (Lifetime: = htmlspecialchars(ini_get('session.cookie_lifetime'), ENT_NOQUOTES, 'UTF-8'); ?>)
@@ -20,8 +20,10 @@ foreach ($_SESSION as $k => $v) { if ($k === '__NF') { $k = 'Nette Session'; $v = isset($v['DATA']) ? $v['DATA'] : NULL; + } elseif ($k === '_tracy') { + continue; } - echo '+'; + +foreach ($rows as $row) { + foreach ($row->panels as $panel) { + $content = $panel->panel ? ($panel->panel . "\n" . $icons) : ''; + $class = 'tracy-panel ' . ($row->type === 'ajax' ? 'tracy-ajax' : ''); ?> + :first-child { - width: 20%; -} - -#tracy-bs tr:nth-child(2n), #tracy-bs tr:nth-child(2n) pre { - background-color: #F7F0CB; -} - -#tracy-bs ol { - margin: 1em 0; - padding-left: 2.5em; -} - -#tracy-bs ul { - font: 7pt/1.5 Verdana, sans-serif !important; - padding: 2em 4em; - margin: 1em 0 0; - color: #777; - background: #F6F5F3; - border-top: 1px solid #DDD; -} - -#tracy-bs-logo a { - position: absolute; - bottom: 0; - right: 0; - width: 100px; - height: 50px; - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat; - opacity: .6; - padding: 0; - margin: 0; -} - -#tracy-bs-logo a:hover, #tracy-bs-logo a:active, #tracy-bs-logo a:focus { - opacity: 1; - transition: opacity 0.1s; -} - - -#tracy-bs div.panel { - padding: 1px 25px; -} - -#tracy-bs div.inner { - background: #F4F3F1; - padding: .1em 1em 1em; - border-radius: 8px; -} - -#tracy-bs .outer { - overflow: auto; -} - - -/* source code */ -#tracy-bs pre.php div { - min-width: 100%; - float: left; - white-space: pre; -} - -#tracy-bs .highlight { - background: #CD1818; - color: white; - font-weight: bold; - font-style: normal; - display: block; - padding: 0 .4em; - margin: 0 -.4em; -} - -#tracy-bs .line { - color: #9F9C7F; - font-weight: normal; - font-style: normal; -} - -#tracy-bs pre:hover span[title] { - border-bottom: 1px solid rgba(0, 0, 0, 0.2); -} - -#tracy-bs a[href^=editor\:] { - color: inherit; - border-bottom: 1px dotted rgba(0, 0, 0, .3); -} - -#tracy-bs span[data-tracy-href] { - border-bottom: 1px dotted rgba(0, 0, 0, .3); -} - - -/* toggle */ -html.tracy-js #tracy-bs .tracy-collapsed { - display: none; -} - -html.tracy-js #tracy-bs .tracy-toggle.tracy-collapsed { - display: inline; -} - -#tracy-bs .tracy-toggle { - cursor: pointer; -} - -#tracy-bs .tracy-toggle:after { - content: " ▼"; - opacity: .4; -} - -#tracy-bs .tracy-toggle.tracy-collapsed:after { - content: " ►"; - opacity: .4; -} - - -/* dump */ -#tracy-bs .tracy-dump-array, #tracy-bs .tracy-dump-object { - color: #C22; -} - -#tracy-bs .tracy-dump-string { - color: #35D; -} - -#tracy-bs .tracy-dump-number { - color: #090; -} - -#tracy-bs .tracy-dump-null, #tracy-bs .tracy-dump-bool { - color: #850; -} - -#tracy-bs .tracy-dump-visibility, #tracy-bs .tracy-dump-hash { - font-size: 85%; - color: #998; -} - -#tracy-bs .tracy-dump-indent { - display: none; -} - -#tracy-bs pre.tracy-dump div { - padding-left: 3ex; -} - -#tracy-bs pre.tracy-dump div div { - border-left: 1px solid rgba(0, 0, 0, .1); - margin-left: .5ex; -} - -#tracy-bs .caused { - float: right; - padding: .3em .6em; - background: #df8075; - border-radius: 0 0 0 8px; - white-space: nowrap; -} - -#tracy-bs .caused a { - color: white; -} +#tracy-bs{font:9pt/1.5 Verdana,sans-serif;background:white;color:#333;position:absolute;z-index:20000;left:0;top:0;width:100%;text-align:left}#tracy-bs *{font:inherit;color:inherit;background:transparent;border:none;margin:0;padding:0;text-align:inherit;text-indent:0}#tracy-bs b{font-weight:bold}#tracy-bs i{font-style:italic}#tracy-bs a{text-decoration:none;color:#328ADC;padding:2px 4px;margin:-2px -4px}#tracy-bs a:hover,#tracy-bs a:active,#tracy-bs a:focus{color:#085AA3}#tracy-bs-toggle{position:absolute;right:.5em;top:.5em;text-decoration:none;background:#CD1818;color:white!important;padding:3px}#tracy-bs-error{background:#CD1818;color:white;font:13pt/1.5 Verdana,sans-serif!important}#tracy-bs-error a{color:white!important;opacity:0;font-size:.7em}#tracy-bs-error:hover a{opacity:.6}#tracy-bs-error a:hover{opacity:1}#tracy-bs-ie-warning{background:black;margin:1em;padding:.5em;text-align:center}#tracy-bs h1{font-size:15pt;font-weight:normal;text-shadow:1px 1px 2px rgba(0,0,0,.3);margin:.7em 0}#tracy-bs h1 span{white-space:pre-wrap}#tracy-bs h2{font:14pt/1.5 sans-serif!important;margin:.6em 0}#tracy-bs h3{font:bold 10pt/1.5 Verdana,sans-serif!important;margin:1em 0;padding:0}#tracy-bs p,#tracy-bs pre{margin:.8em 0}#tracy-bs pre,#tracy-bs code,#tracy-bs table{font:9pt/1.5 Consolas,monospace!important}#tracy-bs pre,#tracy-bs table{background:#FDF5CE;padding:.4em .7em;border:1px dotted silver;overflow:auto}#tracy-bs table pre{padding:0;margin:0;border:none}#tracy-bs table{border-collapse:collapse;width:100%}#tracy-bs td,#tracy-bs th{vertical-align:top;text-align:left;padding:2px 6px;border:1px solid #e6dfbf}#tracy-bs th{font-weight:bold}#tracy-bs tr>:first-child{width:20%}#tracy-bs tr:nth-child(2n),#tracy-bs tr:nth-child(2n) pre{background-color:#F7F0CB}#tracy-bs ol{margin:1em 0;padding-left:2.5em}#tracy-bs ul{font:7pt/1.5 Verdana,sans-serif!important;padding:2em 4em;margin:1em 0 0;color:#777;background:#F6F5F3;border-top:1px solid #DDD}#tracy-bs-logo a{position:absolute;bottom:0;right:0;width:100px;height:50px;background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat;opacity:.6;padding:0;margin:0}#tracy-bs-logo a:hover,#tracy-bs-logo a:active,#tracy-bs-logo a:focus{opacity:1;transition:opacity 0.1s}#tracy-bs div.panel{padding:1px 25px}#tracy-bs div.inner{background:#F4F3F1;padding:.1em 1em 1em;border-radius:8px}#tracy-bs .outer{overflow:auto}#tracy-bs pre.code>div{min-width:100%;float:left;white-space:pre}#tracy-bs .highlight{background:#CD1818;color:white;font-weight:bold;font-style:normal;display:block;padding:0 .4em;margin:0 -.4em}#tracy-bs .line{color:#9F9C7F;font-weight:normal;font-style:normal}#tracy-bs pre:hover span[title]{border-bottom:1px solid rgba(0,0,0,0.2)}#tracy-bs a[href^=editor\:]{color:inherit;border-bottom:1px dotted rgba(0,0,0,.3)}#tracy-bs span[data-tracy-href]{border-bottom:1px dotted rgba(0,0,0,.3)}html.tracy-js #tracy-bs .tracy-collapsed{display:none}html.tracy-js #tracy-bs .tracy-toggle.tracy-collapsed{display:inline}#tracy-bs .tracy-toggle{cursor:pointer}#tracy-bs .tracy-toggle:after{content:" ▼";opacity:.4}#tracy-bs .tracy-toggle.tracy-collapsed:after{content:" ►";opacity:.4}#tracy-bs .tracy-dump-array,#tracy-bs .tracy-dump-object{color:#C22}#tracy-bs .tracy-dump-string{color:#35D}#tracy-bs .tracy-dump-number{color:#090}#tracy-bs .tracy-dump-null,#tracy-bs .tracy-dump-bool{color:#850}#tracy-bs .tracy-dump-visibility,#tracy-bs .tracy-dump-hash{font-size:85%;color:#998}#tracy-bs .tracy-dump-indent{display:none}#tracy-bs pre.tracy-dump div{padding-left:3ex}#tracy-bs pre.tracy-dump div div{border-left:1px solid rgba(0,0,0,.1);margin-left:.5ex}#tracy-bs .caused{float:right;padding:.3em .6em;background:#df8075;border-radius:0 0 0 8px;white-space:nowrap}#tracy-bs .caused a{color:white} \ No newline at end of file diff -Nru php-nette-2.3.10/Nette/Tracy/assets/BlueScreen/bluescreen.js php-nette-2.4-20160731/Nette/Tracy/assets/BlueScreen/bluescreen.js --- php-nette-2.3.10/Nette/Tracy/assets/BlueScreen/bluescreen.js 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/BlueScreen/bluescreen.js 2016-07-31 17:47:02.000000000 +0000 @@ -1,29 +1,3 @@ -/** - * This file is part of the Tracy (https://tracy.nette.org) - */ - -(function(){ - document.body.appendChild(document.getElementById('tracy-bs')); - - for (var i = 0, styles = []; i < document.styleSheets.length; i++) { - var style = document.styleSheets[i]; - if (!style.ownerNode.classList.contains('tracy-debug')) { - style.oldDisabled = style.disabled; - style.disabled = true; - styles.push(style); - } - } - - document.getElementById('tracy-bs-toggle').addEventListener('click', function(e) { - var collapsed = this.classList.contains('tracy-collapsed'); - for (i = 0; i < styles.length; i++) { - styles[i].disabled = collapsed ? true : styles[i].oldDisabled; - } - }); - - document.addEventListener('keyup', function(e) { - if (e.keyCode === 27 && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { // ESC - document.getElementById('tracy-bs-toggle').click(); - } - }); -})(); +(function(){Tracy=window.Tracy||{};var f=Tracy.BlueScreen={},g;f.init=function(b){for(var d=document.getElementById("tracy-bs"),a=0,e=[];a\n"; + echo ' ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', Dumper::toHtml($v, array(Dumper::LIVE => TRUE)), " \n"; }?> diff -Nru php-nette-2.3.10/Nette/Bridges/HttpTracy/templates/SessionPanel.tab.phtml php-nette-2.4-20160731/Nette/Bridges/HttpTracy/templates/SessionPanel.tab.phtml --- php-nette-2.3.10/Nette/Bridges/HttpTracy/templates/SessionPanel.tab.phtml 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/HttpTracy/templates/SessionPanel.tab.phtml 2016-07-31 17:46:36.000000000 +0000 @@ -5,6 +5,6 @@ use Nette; ?> - + diff -Nru php-nette-2.3.10/Nette/Bridges/MailDI/MailExtension.php php-nette-2.4-20160731/Nette/Bridges/MailDI/MailExtension.php --- php-nette-2.3.10/Nette/Bridges/MailDI/MailExtension.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/MailDI/MailExtension.php 2016-07-31 17:46:38.000000000 +0000 @@ -16,7 +16,7 @@ class MailExtension extends Nette\DI\CompilerExtension { - public $defaults = array( + public $defaults = [ 'smtp' => FALSE, 'host' => NULL, 'port' => NULL, @@ -24,25 +24,25 @@ 'password' => NULL, 'secure' => NULL, 'timeout' => NULL, - ); + ]; public function loadConfiguration() { - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); $config = $this->validateConfig($this->defaults); - $mailer = $container->addDefinition($this->prefix('mailer')) - ->setClass('Nette\Mail\IMailer'); + $mailer = $builder->addDefinition($this->prefix('mailer')) + ->setClass(Nette\Mail\IMailer::class); if (empty($config['smtp'])) { - $mailer->setFactory('Nette\Mail\SendmailMailer'); + $mailer->setFactory(Nette\Mail\SendmailMailer::class); } else { - $mailer->setFactory('Nette\Mail\SmtpMailer', array($config)); + $mailer->setFactory(Nette\Mail\SmtpMailer::class, [$config]); } if ($this->name === 'mail') { - $container->addAlias('nette.mailer', $this->prefix('mailer')); + $builder->addAlias('nette.mailer', $this->prefix('mailer')); } } diff -Nru php-nette-2.3.10/Nette/Bridges/Nette/TracyExtension.php php-nette-2.4-20160731/Nette/Bridges/Nette/TracyExtension.php --- php-nette-2.3.10/Nette/Bridges/Nette/TracyExtension.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/Nette/TracyExtension.php 2016-07-31 17:46:44.000000000 +0000 @@ -15,7 +15,7 @@ */ class TracyExtension extends Nette\DI\CompilerExtension { - public $defaults = array( + public $defaults = [ 'email' => NULL, 'fromEmail' => NULL, 'logSeverity' => NULL, @@ -28,33 +28,37 @@ 'maxDepth' => NULL, 'showLocation' => NULL, 'scream' => NULL, - 'bar' => array(), // of class name - 'blueScreen' => array(), // of callback - ); + 'bar' => [], // of class name + 'blueScreen' => [], // of callback + ]; /** @var bool */ private $debugMode; + /** @var bool */ + private $cliMode; + - public function __construct($debugMode = FALSE) + public function __construct($debugMode = FALSE, $cliMode = FALSE) { $this->debugMode = $debugMode; + $this->cliMode = $cliMode; } public function loadConfiguration() { $this->validateConfig($this->defaults); - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); - $container->addDefinition($this->prefix('logger')) + $builder->addDefinition($this->prefix('logger')) ->setClass('Tracy\ILogger') ->setFactory('Tracy\Debugger::getLogger'); - $container->addDefinition($this->prefix('blueScreen')) + $builder->addDefinition($this->prefix('blueScreen')) ->setFactory('Tracy\Debugger::getBlueScreen'); - $container->addDefinition($this->prefix('bar')) + $builder->addDefinition($this->prefix('bar')) ->setFactory('Tracy\Debugger::getBar'); } @@ -62,36 +66,52 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) { $initialize = $class->getMethod('initialize'); - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); $options = $this->config; unset($options['bar'], $options['blueScreen']); + if (isset($options['logSeverity'])) { + $res = 0; + foreach ((array) $options['logSeverity'] as $level) { + $res |= is_int($level) ? $level : constant($level); + } + $options['logSeverity'] = $res; + } foreach ($options as $key => $value) { if ($value !== NULL) { $key = ($key === 'fromEmail' ? 'getLogger()->' : '$') . $key; - $initialize->addBody($container->formatPhp( + $initialize->addBody($builder->formatPhp( 'Tracy\Debugger::' . $key . ' = ?;', - Nette\DI\Compiler::filterArguments(array($value)) + Nette\DI\Compiler::filterArguments([$value]) )); } } + $logger = $builder->getDefinition($this->prefix('logger')); + if ($logger->getFactory()->getEntity() !== 'Tracy\Debugger::getLogger') { + $initialize->addBody($builder->formatPhp('Tracy\Debugger::setLogger(?);', [$logger])); + } + if ($this->debugMode) { foreach ((array) $this->config['bar'] as $item) { - $initialize->addBody($container->formatPhp( + $initialize->addBody($builder->formatPhp( '$this->getService(?)->addPanel(?);', - Nette\DI\Compiler::filterArguments(array( + Nette\DI\Compiler::filterArguments([ $this->prefix('bar'), is_string($item) ? new Nette\DI\Statement($item) : $item, - )) + ]) )); } + + if (!$this->cliMode) { + $initialize->addBody('if ($tmp = $this->getByType("Nette\Http\Session", FALSE)) { $tmp->start(); Tracy\Debugger::dispatch(); };'); + } } foreach ((array) $this->config['blueScreen'] as $item) { - $initialize->addBody($container->formatPhp( + $initialize->addBody($builder->formatPhp( '$this->getService(?)->addPanel(?);', - Nette\DI\Compiler::filterArguments(array($this->prefix('blueScreen'), $item)) + Nette\DI\Compiler::filterArguments([$this->prefix('blueScreen'), $item]) )); } } diff -Nru php-nette-2.3.10/Nette/Bridges/ReflectionDI/ReflectionExtension.php php-nette-2.4-20160731/Nette/Bridges/ReflectionDI/ReflectionExtension.php --- php-nette-2.3.10/Nette/Bridges/ReflectionDI/ReflectionExtension.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/ReflectionDI/ReflectionExtension.php 2016-07-31 17:46:40.000000000 +0000 @@ -28,8 +28,8 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) { $class->getMethod('initialize') - ->addBody('Nette\Reflection\AnnotationsParser::setCacheStorage($this->getByType("Nette\Caching\IStorage"));') - ->addBody('Nette\Reflection\AnnotationsParser::$autoRefresh = ?;', array($this->debugMode)); + ->addBody('Nette\Reflection\AnnotationsParser::setCacheStorage($this->getByType(Nette\Caching\IStorage::class));') + ->addBody('Nette\Reflection\AnnotationsParser::$autoRefresh = ?;', [$this->debugMode]); } } diff -Nru php-nette-2.3.10/Nette/Bridges/SecurityDI/SecurityExtension.php php-nette-2.4-20160731/Nette/Bridges/SecurityDI/SecurityExtension.php --- php-nette-2.3.10/Nette/Bridges/SecurityDI/SecurityExtension.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/SecurityDI/SecurityExtension.php 2016-07-31 17:46:42.000000000 +0000 @@ -1,8 +1,8 @@ TRUE, - 'users' => array(), // of [user => password] or [user => ['password' => password, 'roles' => [role]]] - 'roles' => array(), // of [role => parents] - 'resources' => array(), // of [resource => parents] - ); + 'users' => [], // of [user => password] or [user => ['password' => password, 'roles' => [role]]] + 'roles' => [], // of [role => parents] + 'resources' => [], // of [resource => parents] + ]; /** @var bool */ private $debugMode; @@ -35,59 +35,59 @@ public function loadConfiguration() { $config = $this->validateConfig($this->defaults); - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); - $container->addDefinition($this->prefix('userStorage')) - ->setClass('Nette\Security\IUserStorage') - ->setFactory('Nette\Http\UserStorage'); + $builder->addDefinition($this->prefix('userStorage')) + ->setClass(Nette\Security\IUserStorage::class) + ->setFactory(Nette\Http\UserStorage::class); - $user = $container->addDefinition($this->prefix('user')) - ->setClass('Nette\Security\User'); + $user = $builder->addDefinition($this->prefix('user')) + ->setClass(Nette\Security\User::class); if ($this->debugMode && $config['debugger']) { - $user->addSetup('@Tracy\Bar::addPanel', array( - new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel'), - )); + $user->addSetup('@Tracy\Bar::addPanel', [ + new Nette\DI\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), + ]); } if ($config['users']) { - $usersList = $usersRoles = array(); + $usersList = $usersRoles = []; foreach ($config['users'] as $username => $data) { - $data = is_array($data) ? $data : array('password' => $data); - $this->validateConfig(array('password' => NULL, 'roles' => NULL), $data, $this->prefix("security.users.$username")); + $data = is_array($data) ? $data : ['password' => $data]; + $this->validateConfig(['password' => NULL, 'roles' => NULL], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; $usersRoles[$username] = isset($data['roles']) ? $data['roles'] : NULL; } - $container->addDefinition($this->prefix('authenticator')) - ->setClass('Nette\Security\IAuthenticator') - ->setFactory('Nette\Security\SimpleAuthenticator', array($usersList, $usersRoles)); + $builder->addDefinition($this->prefix('authenticator')) + ->setClass(Nette\Security\IAuthenticator::class) + ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles]); if ($this->name === 'security') { - $container->addAlias('nette.authenticator', $this->prefix('authenticator')); + $builder->addAlias('nette.authenticator', $this->prefix('authenticator')); } } if ($config['roles'] || $config['resources']) { - $authorizator = $container->addDefinition($this->prefix('authorizator')) - ->setClass('Nette\Security\IAuthorizator') - ->setFactory('Nette\Security\Permission'); + $authorizator = $builder->addDefinition($this->prefix('authorizator')) + ->setClass(Nette\Security\IAuthorizator::class) + ->setFactory(Nette\Security\Permission::class); foreach ($config['roles'] as $role => $parents) { - $authorizator->addSetup('addRole', array($role, $parents)); + $authorizator->addSetup('addRole', [$role, $parents]); } foreach ($config['resources'] as $resource => $parents) { - $authorizator->addSetup('addResource', array($resource, $parents)); + $authorizator->addSetup('addResource', [$resource, $parents]); } if ($this->name === 'security') { - $container->addAlias('nette.authorizator', $this->prefix('authorizator')); + $builder->addAlias('nette.authorizator', $this->prefix('authorizator')); } } if ($this->name === 'security') { - $container->addAlias('user', $this->prefix('user')); - $container->addAlias('nette.userStorage', $this->prefix('userStorage')); + $builder->addAlias('user', $this->prefix('user')); + $builder->addAlias('nette.userStorage', $this->prefix('userStorage')); } } diff -Nru php-nette-2.3.10/Nette/Bridges/SecurityTracy/templates/UserPanel.panel.phtml php-nette-2.4-20160731/Nette/Bridges/SecurityTracy/templates/UserPanel.panel.phtml --- php-nette-2.3.10/Nette/Bridges/SecurityTracy/templates/UserPanel.panel.phtml 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/SecurityTracy/templates/UserPanel.panel.phtml 2016-07-31 17:46:42.000000000 +0000 @@ -9,5 +9,5 @@ ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', Dumper::toHtml($v, [Dumper::LIVE => TRUE]), " diff -Nru php-nette-2.3.10/Nette/Bridges/SecurityTracy/templates/UserPanel.tab.phtml php-nette-2.4-20160731/Nette/Bridges/SecurityTracy/templates/UserPanel.tab.phtml --- php-nette-2.3.10/Nette/Bridges/SecurityTracy/templates/UserPanel.tab.phtml 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/SecurityTracy/templates/UserPanel.tab.phtml 2016-07-31 17:46:42.000000000 +0000 @@ -5,6 +5,6 @@ use Nette; ?> - - + + diff -Nru php-nette-2.3.10/Nette/Bridges/SecurityTracy/UserPanel.php php-nette-2.4-20160731/Nette/Bridges/SecurityTracy/UserPanel.php --- php-nette-2.3.10/Nette/Bridges/SecurityTracy/UserPanel.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Bridges/SecurityTracy/UserPanel.php 2016-07-31 17:46:42.000000000 +0000 @@ -1,8 +1,8 @@ user; require __DIR__ . '/templates/UserPanel.tab.phtml'; return ob_get_clean(); @@ -49,7 +51,7 @@ */ public function getPanel() { - ob_start(); + ob_start(function () {}); $user = $this->user; require __DIR__ . '/templates/UserPanel.panel.phtml'; return ob_get_clean(); diff -Nru php-nette-2.3.10/Nette/Caching/Cache.php php-nette-2.4-20160731/Nette/Caching/Cache.php --- php-nette-2.3.10/Nette/Caching/Cache.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Cache.php 2016-07-31 17:46:32.000000000 +0000 @@ -14,8 +14,10 @@ /** * Implements the cache for a application. */ -class Cache extends Nette\Object implements \ArrayAccess +class Cache { + use Nette\SmartObject; + /** dependency */ const PRIORITY = 'priority', EXPIRATION = 'expire', @@ -37,12 +39,6 @@ /** @var string */ private $namespace; - /** @var string last query cache used by offsetGet() */ - private $key; - - /** @var mixed last query cache used by offsetGet() */ - private $data; - public function __construct(IStorage $storage, $namespace = NULL) { @@ -94,7 +90,7 @@ $data = $this->storage->read($this->generateKey($key)); if ($data === NULL && $fallback) { return $this->save($key, function (& $dependencies) use ($fallback) { - return call_user_func_array($fallback, array(& $dependencies)); + return call_user_func_array($fallback, [& $dependencies]); }); } return $data; @@ -102,6 +98,55 @@ /** + * Reads multiple items from the cache. + * @param array + * @param callable + * @return array + */ + public function bulkLoad(array $keys, $fallback = NULL) + { + if (count($keys) === 0) { + return []; + } + foreach ($keys as $key) { + if (!is_scalar($key)) { + throw new Nette\InvalidArgumentException('Only scalar keys are allowed in bulkLoad()'); + } + } + $storageKeys = array_map([$this, 'generateKey'], $keys); + if (!$this->storage instanceof IBulkReader) { + $result = array_combine($keys, array_map([$this->storage, 'read'], $storageKeys)); + if ($fallback !== NULL) { + foreach ($result as $key => $value) { + if ($value === NULL) { + $result[$key] = $this->save($key, function (& $dependencies) use ($key, $fallback) { + return call_user_func_array($fallback, [$key, & $dependencies]); + }); + } + } + } + return $result; + } + + $cacheData = $this->storage->bulkRead($storageKeys); + $result = []; + foreach ($keys as $i => $key) { + $storageKey = $storageKeys[$i]; + if (isset($cacheData[$storageKey])) { + $result[$key] = $cacheData[$storageKey]; + } elseif ($fallback) { + $result[$key] = $this->save($key, function (& $dependencies) use ($key, $fallback) { + return call_user_func_array($fallback, [$key, & $dependencies]); + }); + } else { + $result[$key] = NULL; + } + } + return $result; + } + + + /** * Writes item into the cache. * Dependencies are: * - Cache::PRIORITY => (int) priority @@ -120,13 +165,15 @@ */ public function save($key, $data, array $dependencies = NULL) { - $this->key = $this->data = NULL; $key = $this->generateKey($key); if ($data instanceof Nette\Callback || $data instanceof \Closure) { + if ($data instanceof Nette\Callback) { + trigger_error('Nette\Callback is deprecated, use closure or Nette\Utils\Callback::toClosure().', E_USER_DEPRECATED); + } $this->storage->lock($key); try { - $data = call_user_func_array($data, array(& $dependencies)); + $data = call_user_func_array($data, [& $dependencies]); } catch (\Throwable $e) { $this->storage->remove($key); throw $e; @@ -139,13 +186,13 @@ if ($data === NULL) { $this->storage->remove($key); } else { - $this->storage->write($key, $data, $this->completeDependencies($dependencies, $data)); + $this->storage->write($key, $data, $this->completeDependencies($dependencies)); return $data; } } - private function completeDependencies($dp, $data) + private function completeDependencies($dp) { // convert expire into relative amount of seconds if (isset($dp[self::EXPIRATION])) { @@ -155,26 +202,26 @@ // convert FILES into CALLBACKS if (isset($dp[self::FILES])) { foreach (array_unique((array) $dp[self::FILES]) as $item) { - $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkFile'), $item, @filemtime($item)); // @ - stat may fail + $dp[self::CALLBACKS][] = [[__CLASS__, 'checkFile'], $item, @filemtime($item)]; // @ - stat may fail } unset($dp[self::FILES]); } // add namespaces to items if (isset($dp[self::ITEMS])) { - $dp[self::ITEMS] = array_unique(array_map(array($this, 'generateKey'), (array) $dp[self::ITEMS])); + $dp[self::ITEMS] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::ITEMS])); } // convert CONSTS into CALLBACKS if (isset($dp[self::CONSTS])) { foreach (array_unique((array) $dp[self::CONSTS]) as $item) { - $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkConst'), $item, constant($item)); + $dp[self::CALLBACKS][] = [[__CLASS__, 'checkConst'], $item, constant($item)]; } unset($dp[self::CONSTS]); } if (!is_array($dp)) { - $dp = array(); + $dp = []; } return $dp; } @@ -201,7 +248,6 @@ */ public function clean(array $conditions = NULL) { - $this->key = $this->data = NULL; $this->storage->clean((array) $conditions); } @@ -231,15 +277,14 @@ */ public function wrap($function, array $dependencies = NULL) { - $cache = $this; - return function () use ($cache, $function, $dependencies) { - $key = array($function, func_get_args()); + return function () use ($function, $dependencies) { + $key = [$function, func_get_args()]; if (is_array($function) && is_object($function[0])) { $key[0][0] = get_class($function[0]); } - $data = $cache->load($key); + $data = $this->load($key); if ($data === NULL) { - $data = $cache->save($key, Callback::invokeArgs($function, $key[1]), $dependencies); + $data = $this->save($key, Callback::invokeArgs($function, $key[1]), $dependencies); } return $data; }; @@ -273,65 +318,6 @@ } - /********************* interface ArrayAccess ****************d*g**/ - - - /** - * @deprecated - */ - public function offsetSet($key, $data) - { - trigger_error('Using [] is deprecated; use Cache::save(key, data) instead.', E_USER_DEPRECATED); - $this->save($key, $data); - } - - - /** - * @deprecated - */ - public function offsetGet($key) - { - trigger_error('Using [] is deprecated; use Cache::load(key) instead.', E_USER_DEPRECATED); - $key = is_scalar($key) ? (string) $key : serialize($key); - if ($this->key !== $key) { - $this->key = $key; - $this->data = $this->load($key); - } - return $this->data; - } - - - /** - * @deprecated - */ - public function offsetExists($key) - { - trigger_error('Using [] is deprecated; use Cache::load(key) !== NULL instead.', E_USER_DEPRECATED); - $this->key = $this->data = NULL; - return $this->offsetGet($key) !== NULL; - } - - - /** - * @deprecated - */ - public function offsetUnset($key) - { - trigger_error('Using [] is deprecated; use Cache::remove(key) instead.', E_USER_DEPRECATED); - $this->save($key, NULL); - } - - - /** - * @deprecated - */ - public function release() - { - trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); - $this->key = $this->data = NULL; - } - - /********************* dependency checkers ****************d*g**/ diff -Nru php-nette-2.3.10/Nette/Caching/IBulkReader.php php-nette-2.4-20160731/Nette/Caching/IBulkReader.php --- php-nette-2.3.10/Nette/Caching/IBulkReader.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/IBulkReader.php 2016-07-31 17:46:32.000000000 +0000 @@ -0,0 +1,24 @@ + value pairs, missing items are omitted + */ + function bulkRead(array $keys); + +} diff -Nru php-nette-2.3.10/Nette/Caching/OutputHelper.php php-nette-2.4-20160731/Nette/Caching/OutputHelper.php --- php-nette-2.3.10/Nette/Caching/OutputHelper.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/OutputHelper.php 2016-07-31 17:46:32.000000000 +0000 @@ -13,8 +13,10 @@ /** * Output caching helper. */ -class OutputHelper extends Nette\Object +class OutputHelper { + use Nette\SmartObject; + /** @var array */ public $dependencies; diff -Nru php-nette-2.3.10/Nette/Caching/Storages/DevNullStorage.php php-nette-2.4-20160731/Nette/Caching/Storages/DevNullStorage.php --- php-nette-2.3.10/Nette/Caching/Storages/DevNullStorage.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/DevNullStorage.php 2016-07-31 17:46:32.000000000 +0000 @@ -13,8 +13,9 @@ /** * Cache dummy storage. */ -class DevNullStorage extends Nette\Object implements Nette\Caching\IStorage +class DevNullStorage implements Nette\Caching\IStorage { + use Nette\SmartObject; /** * Read from cache. diff -Nru php-nette-2.3.10/Nette/Caching/Storages/FileJournal.php php-nette-2.4-20160731/Nette/Caching/Storages/FileJournal.php --- php-nette-2.3.10/Nette/Caching/Storages/FileJournal.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/FileJournal.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,1202 +0,0 @@ - 0, - self::PRIORITY => 1, - self::ENTRIES => 2, - self::DATA => 3, - ); - - - /** - * @param string Directory containing journal file - */ - public function __construct($dir) - { - $this->file = $dir . '/' . self::FILE; - } - - - /** - * @return void - */ - public function __destruct() - { - if ($this->handle) { - $this->headerCommit(); - flock($this->handle, LOCK_UN); // Since PHP 5.3.3 is manual unlock necessary - fclose($this->handle); - $this->handle = FALSE; - } - } - - - /** - * Writes entry information into the journal. - * @param string - * @param array - * @return void - */ - public function write($key, array $dependencies) - { - $this->lock(); - - $priority = !isset($dependencies[Cache::PRIORITY]) ? FALSE : (int) $dependencies[Cache::PRIORITY]; - $tags = empty($dependencies[Cache::TAGS]) ? FALSE : (array) $dependencies[Cache::TAGS]; - - $exists = FALSE; - $keyHash = crc32($key); - list($entriesNodeId, $entriesNode) = $this->findIndexNode(self::ENTRIES, $keyHash); - - if (isset($entriesNode[$keyHash])) { - $entries = $this->mergeIndexData($entriesNode[$keyHash]); - foreach ($entries as $link => $foo) { - $dataNode = $this->getNode($link >> self::BITROT); - if ($dataNode[$link][self::KEY] === $key) { - if ($dataNode[$link][self::TAGS] == $tags && $dataNode[$link][self::PRIORITY] === $priority) { // intentionally ==, the order of tags does not matter - if ($dataNode[$link][self::DELETED]) { - $dataNode[$link][self::DELETED] = FALSE; - $this->saveNode($link >> self::BITROT, $dataNode); - } - $exists = TRUE; - } else { // Already exists, but with other tags or priority - $toDelete = array(); - foreach ($dataNode[$link][self::TAGS] as $tag) { - $toDelete[self::TAGS][$tag][$link] = TRUE; - } - if ($dataNode[$link][self::PRIORITY] !== FALSE) { - $toDelete[self::PRIORITY][$dataNode[$link][self::PRIORITY]][$link] = TRUE; - } - $toDelete[self::ENTRIES][$keyHash][$link] = TRUE; - $this->cleanFromIndex($toDelete); - - unset($dataNode[$link]); - $this->saveNode($link >> self::BITROT, $dataNode); - - // Node was changed but may be empty, find it again - list($entriesNodeId, $entriesNode) = $this->findIndexNode(self::ENTRIES, $keyHash); - } - break; - } - } - } - - if ($exists === FALSE) { - // Magical constants - $requiredSize = strlen($key) + 75; - if ($tags) { - foreach ($tags as $tag) { - $requiredSize += strlen($tag) + 13; - } - } - $requiredSize += $priority ? 10 : 1; - - $freeDataNode = $this->findFreeDataNode($requiredSize); - $data = $this->getNode($freeDataNode); - - if ($data === FALSE) { - $data = array( - self::INFO => array( - self::LAST_INDEX => ($freeDataNode << self::BITROT), - self::TYPE => self::DATA, - ), - ); - } - - $dataNodeKey = $this->findNextFreeKey($freeDataNode, $data); - $data[$dataNodeKey] = array( - self::KEY => $key, - self::TAGS => $tags ? $tags : array(), - self::PRIORITY => $priority, - self::DELETED => FALSE, - ); - - $this->saveNode($freeDataNode, $data); - - // Save to entries tree, ... - $entriesNode[$keyHash][$dataNodeKey] = 1; - $this->saveNode($entriesNodeId, $entriesNode); - - // ...tags tree... - if ($tags) { - foreach ($tags as $tag) { - list($nodeId, $node) = $this->findIndexNode(self::TAGS, $tag); - $node[$tag][$dataNodeKey] = 1; - $this->saveNode($nodeId, $node); - } - } - - // ...and priority tree. - if ($priority !== FALSE) { - list($nodeId, $node) = $this->findIndexNode(self::PRIORITY, $priority); - $node[$priority][$dataNodeKey] = 1; - $this->saveNode($nodeId, $node); - } - } - - $this->commit(); - $this->unlock(); - } - - - /** - * Cleans entries from journal. - * @param array - * @return array of removed items or NULL when performing a full cleanup - */ - public function clean(array $conditions) - { - $this->lock(); - - if (!empty($conditions[Cache::ALL])) { - $this->nodeCache = $this->nodeChanged = $this->dataNodeFreeSpace = array(); - $this->deleteAll(); - $this->unlock(); - return NULL; - } - - $toDelete = array( - self::TAGS => array(), - self::PRIORITY => array(), - self::ENTRIES => array(), - ); - - $entries = array(); - - if (!empty($conditions[Cache::TAGS])) { - $entries = $this->cleanTags((array) $conditions[Cache::TAGS], $toDelete); - } - - if (isset($conditions[Cache::PRIORITY])) { - $this->arrayAppend($entries, $this->cleanPriority((int) $conditions[Cache::PRIORITY], $toDelete)); - } - - $this->deletedLinks = array(); - $this->cleanFromIndex($toDelete); - - $this->commit(); - $this->unlock(); - - return $entries; - } - - - /** - * Cleans entries from journal by tags. - * @param array - * @param array - * @return array of removed items - */ - private function cleanTags(array $tags, array & $toDelete) - { - $entries = array(); - - foreach ($tags as $tag) { - list(, $node) = $this->findIndexNode(self::TAGS, $tag); - - if (isset($node[$tag])) { - $ent = $this->cleanLinks($this->mergeIndexData($node[$tag]), $toDelete); - $this->arrayAppend($entries, $ent); - } - } - - return $entries; - } - - - /** - * Cleans entries from journal by priority. - * @param int - * @param array - * @return array of removed items - */ - private function cleanPriority($priority, array & $toDelete) - { - list(, $node) = $this->findIndexNode(self::PRIORITY, $priority); - - ksort($node); - - $allData = array(); - - foreach ($node as $prior => $data) { - if ($prior === self::INFO) { - continue; - } elseif ($prior > $priority) { - break; - } - - $this->arrayAppendKeys($allData, $this->mergeIndexData($data)); - } - - $nodeInfo = $node[self::INFO]; - while ($nodeInfo[self::PREV_NODE] !== -1) { - $nodeId = $nodeInfo[self::PREV_NODE]; - $node = $this->getNode($nodeId); - - if ($node === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $nodeId."); - } - break; - } - - $nodeInfo = $node[self::INFO]; - unset($node[self::INFO]); - - foreach ($node as $data) { - $this->arrayAppendKeys($allData, $this->mergeIndexData($data)); - } - } - - return $this->cleanLinks($allData, $toDelete); - } - - - /** - * Cleans links from $data. - * @param array - * @param array - * @return array of removed items - */ - private function cleanLinks(array $data, array & $toDelete) - { - $return = array(); - - $data = array_keys($data); - sort($data); - $max = count($data); - $data[] = 0; - $i = 0; - - while ($i < $max) { - $searchLink = $data[$i]; - - if (isset($this->deletedLinks[$searchLink])) { - ++$i; - continue; - } - - $nodeId = $searchLink >> self::BITROT; - $node = $this->getNode($nodeId); - - if ($node === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $nodeId."); - } - ++$i; - continue; - } - - do { - $link = $data[$i]; - - if (isset($this->deletedLinks[$link])) { - continue; - } elseif (!isset($node[$link])) { - if (self::$debug) { - throw new Nette\InvalidStateException("Link with ID $searchLink is not in node $nodeId."); - } - continue; - } - - $nodeLink = & $node[$link]; - if (!$nodeLink[self::DELETED]) { - $nodeLink[self::DELETED] = TRUE; - $return[] = $nodeLink[self::KEY]; - } else { - foreach ($nodeLink[self::TAGS] as $tag) { - $toDelete[self::TAGS][$tag][$link] = TRUE; - } - if ($nodeLink[self::PRIORITY] !== FALSE) { - $toDelete[self::PRIORITY][$nodeLink[self::PRIORITY]][$link] = TRUE; - } - $toDelete[self::ENTRIES][crc32($nodeLink[self::KEY])][$link] = TRUE; - unset($node[$link]); - $this->deletedLinks[$link] = TRUE; - } - } while (($data[++$i] >> self::BITROT) === $nodeId); - - $this->saveNode($nodeId, $node); - } - - return $return; - } - - - /** - * Remove links to deleted keys from index. - * @param array - */ - private function cleanFromIndex(array $toDeleteFromIndex) - { - foreach ($toDeleteFromIndex as $type => $toDelete) { - ksort($toDelete); - - while (!empty($toDelete)) { - reset($toDelete); - $searchKey = key($toDelete); - list($masterNodeId, $masterNode) = $this->findIndexNode($type, $searchKey); - - if (!isset($masterNode[$searchKey])) { - if (self::$debug) { - throw new Nette\InvalidStateException('Bad index.'); - } - unset($toDelete[$searchKey]); - continue; - } - - foreach ($toDelete as $key => $links) { - if (isset($masterNode[$key])) { - foreach ($links as $link => $foo) { - if (isset($masterNode[$key][$link])) { - unset($masterNode[$key][$link], $links[$link]); - } - } - - if (!empty($links) && isset($masterNode[$key][self::INDEX_DATA])) { - $this->cleanIndexData($masterNode[$key][self::INDEX_DATA], $links, $masterNode[$key]); - } - - if (empty($masterNode[$key])) { - unset($masterNode[$key]); - } - unset($toDelete[$key]); - } else { - break; - } - } - $this->saveNode($masterNodeId, $masterNode); - } - } - } - - - /** - * Merge data with index data in other nodes. - * @param array - * @return array of merged items - */ - private function mergeIndexData(array $data) - { - while (isset($data[self::INDEX_DATA])) { - $id = $data[self::INDEX_DATA]; - unset($data[self::INDEX_DATA]); - $childNode = $this->getNode($id); - - if ($childNode === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $id."); - } - break; - } - - $this->arrayAppendKeys($data, $childNode[self::INDEX_DATA]); - } - - return $data; - } - - - /** - * Cleans links from other nodes. - * @param int - * @param array - * @param array - * @return void - */ - private function cleanIndexData($nextNodeId, array $links, & $masterNodeLink) - { - $prev = -1; - - while ($nextNodeId && !empty($links)) { - $nodeId = $nextNodeId; - $node = $this->getNode($nodeId); - - if ($node === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $nodeId."); - } - break; - } - - foreach ($links as $link => $foo) { - if (isset($node[self::INDEX_DATA][$link])) { - unset($node[self::INDEX_DATA][$link], $links[$link]); - } - } - - if (isset($node[self::INDEX_DATA][self::INDEX_DATA])) { - $nextNodeId = $node[self::INDEX_DATA][self::INDEX_DATA]; - } else { - $nextNodeId = FALSE; - } - - if (empty($node[self::INDEX_DATA]) || (count($node[self::INDEX_DATA]) === 1 && $nextNodeId)) { - if ($prev === -1) { - if ($nextNodeId === FALSE) { - unset($masterNodeLink[self::INDEX_DATA]); - } else { - $masterNodeLink[self::INDEX_DATA] = $nextNodeId; - } - } else { - $prevNode = $this->getNode($prev); - if ($prevNode === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $prev."); - } - } else { - if ($nextNodeId === FALSE) { - unset($prevNode[self::INDEX_DATA][self::INDEX_DATA]); - if (empty($prevNode[self::INDEX_DATA])) { - unset($prevNode[self::INDEX_DATA]); - } - } else { - $prevNode[self::INDEX_DATA][self::INDEX_DATA] = $nextNodeId; - } - - $this->saveNode($prev, $prevNode); - } - } - unset($node[self::INDEX_DATA]); - } else { - $prev = $nodeId; - } - - $this->saveNode($nodeId, $node); - } - } - - - /** - * Get node from journal. - * @param int - * @return array - */ - private function getNode($id) - { - // Load from cache - if (isset($this->nodeCache[$id])) { - return $this->nodeCache[$id]; - } - - $binary = stream_get_contents($this->handle, self::NODE_SIZE, self::HEADER_SIZE + self::NODE_SIZE * $id); - - if (empty($binary)) { - // empty node, no Exception - return FALSE; - } - - list(, $magic, $length) = unpack('N2', $binary); - if ($magic !== self::INDEX_MAGIC && $magic !== self::DATA_MAGIC) { - if (!empty($magic)) { - if (self::$debug) { - throw new Nette\InvalidStateException("Node $id has malformed header."); - } - $this->deleteNode($id); - } - return FALSE; - } - - $data = substr($binary, 2 * self::INT32_SIZE, $length - 2 * self::INT32_SIZE); - - $node = @unserialize($data); // intentionally @ - if ($node === FALSE) { - $this->deleteNode($id); - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot unserialize node number $id."); - } - return FALSE; - } - - // Save to cache and return - return $this->nodeCache[$id] = $node; - } - - - /** - * Save node to cache. - * @param int - * @param array - * @return void - */ - private function saveNode($id, array $node) - { - if (count($node) === 1) { // Nod contains only INFO - $nodeInfo = $node[self::INFO]; - if ($nodeInfo[self::TYPE] !== self::DATA) { - - if ($nodeInfo[self::END] !== -1) { - $this->nodeCache[$id] = $node; - $this->nodeChanged[$id] = TRUE; - return; - } - - if ($nodeInfo[self::MAX] === -1) { - $max = PHP_INT_MAX; - } else { - $max = $nodeInfo[self::MAX]; - } - - list(, , $parentId) = $this->findIndexNode($nodeInfo[self::TYPE], $max, $id); - if ($parentId !== -1 && $parentId !== $id) { - $parentNode = $this->getNode($parentId); - if ($parentNode === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $parentId."); - } - } else { - if ($parentNode[self::INFO][self::END] === $id) { - if (count($parentNode) === 1) { - $parentNode[self::INFO][self::END] = -1; - } else { - end($parentNode); - $lastKey = key($parentNode); - $parentNode[self::INFO][self::END] = $parentNode[$lastKey]; - unset($parentNode[$lastKey]); - } - } else { - unset($parentNode[$nodeInfo[self::MAX]]); - } - - $this->saveNode($parentId, $parentNode); - } - } - - if ($nodeInfo[self::TYPE] === self::PRIORITY) { // only priority tree has link to prevNode - if ($nodeInfo[self::MAX] === -1) { - if ($nodeInfo[self::PREV_NODE] !== -1) { - $prevNode = $this->getNode($nodeInfo[self::PREV_NODE]); - if ($prevNode === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number {$nodeInfo[self::PREV_NODE]}."); - } - } else { - $prevNode[self::INFO][self::MAX] = -1; - $this->saveNode($nodeInfo[self::PREV_NODE], $prevNode); - } - } - } else { - list($nextId, $nextNode) = $this->findIndexNode($nodeInfo[self::TYPE], $nodeInfo[self::MAX] + 1, NULL, $id); - if ($nextId !== -1 && $nextId !== $id) { - $nextNode[self::INFO][self::PREV_NODE] = $nodeInfo[self::PREV_NODE]; - $this->saveNode($nextId, $nextNode); - } - } - } - } - $this->nodeCache[$id] = FALSE; - } else { - $this->nodeCache[$id] = $node; - } - $this->nodeChanged[$id] = TRUE; - } - - - /** - * Commit all changed nodes from cache to journal file. - * @return void - */ - private function commit() - { - do { - foreach ($this->nodeChanged as $id => $foo) { - if ($this->prepareNode($id, $this->nodeCache[$id])) { - unset($this->nodeChanged[$id]); - } - } - } while (!empty($this->nodeChanged)); - - foreach ($this->toCommit as $node => $str) { - $this->commitNode($node, $str); - } - $this->toCommit = array(); - } - - - /** - * Prepare node to journal file structure. - * @param int - * @param array|bool - * @return bool Successfully committed - */ - private function prepareNode($id, $node) - { - if ($node === FALSE) { - if ($id < $this->lastNode) { - $this->lastNode = $id; - } - unset($this->nodeCache[$id]); - unset($this->dataNodeFreeSpace[$id]); - $this->deleteNode($id); - return TRUE; - } - - $data = serialize($node); - $dataSize = strlen($data) + 2 * self::INT32_SIZE; - - $isData = $node[self::INFO][self::TYPE] === self::DATA; - if ($dataSize > self::NODE_SIZE) { - if ($isData) { - throw new Nette\InvalidStateException('Saving node is bigger than maximum node size.'); - } else { - $this->bisectNode($id, $node); - return FALSE; - } - } - - $this->toCommit[$id] = pack('N2', $isData ? self::DATA_MAGIC : self::INDEX_MAGIC, $dataSize) . $data; - - if ($this->lastNode < $id) { - $this->lastNode = $id; - } - if ($isData) { - $this->dataNodeFreeSpace[$id] = self::NODE_SIZE - $dataSize; - } - - return TRUE; - } - - - /** - * Commit node string to journal file. - * @param int - * @param string - * @return void - */ - private function commitNode($id, $str) - { - fseek($this->handle, self::HEADER_SIZE + self::NODE_SIZE * $id); - $written = fwrite($this->handle, $str); - if ($written === FALSE) { - throw new Nette\InvalidStateException("Cannot write node number $id to journal."); - } - } - - - /** - * Find right node in B+tree. . - * @param string Tree type (TAGS, PRIORITY or ENTRIES) - * @param int Searched item - * @return array Node - */ - private function findIndexNode($type, $search, $childId = NULL, $prevId = NULL) - { - $nodeId = self::$startNode[$type]; - - $parentId = -1; - while (TRUE) { - $node = $this->getNode($nodeId); - - if ($node === FALSE) { - return array( - $nodeId, - array( - self::INFO => array( - self::TYPE => $type, - self::IS_LEAF => TRUE, - self::PREV_NODE => -1, - self::END => -1, - self::MAX => -1, - ), - ), - $parentId, - ); // Init empty node - } - - if ($node[self::INFO][self::IS_LEAF] || $nodeId === $childId || $node[self::INFO][self::PREV_NODE] === $prevId) { - return array($nodeId, $node, $parentId); - } - - $parentId = $nodeId; - - if (isset($node[$search])) { - $nodeId = $node[$search]; - } else { - foreach ($node as $key => $childNode) { - if ($key > $search && $key !== self::INFO) { - $nodeId = $childNode; - continue 2; - } - } - - $nodeId = $node[self::INFO][self::END]; - } - } - } - - - /** - * Find complete free node. - * @param int - * @return array|int Node ID - */ - private function findFreeNode($count = 1) - { - $id = $this->lastNode; - $nodesId = array(); - - do { - if (isset($this->nodeCache[$id])) { - ++$id; - continue; - } - - $offset = self::HEADER_SIZE + self::NODE_SIZE * $id; - - $binary = stream_get_contents($this->handle, self::INT32_SIZE, $offset); - - if (empty($binary)) { - $nodesId[] = $id; - } else { - list(, $magic) = unpack('N', $binary); - if ($magic !== self::INDEX_MAGIC && $magic !== self::DATA_MAGIC) { - $nodesId[] = $id; - } - } - - ++$id; - } while (count($nodesId) !== $count); - - if ($count === 1) { - return $nodesId[0]; - } else { - return $nodesId; - } - } - - - /** - * Find free data node that has $size bytes of free space. - * @param int size in bytes - * @return int Node ID - */ - private function findFreeDataNode($size) - { - foreach ($this->dataNodeFreeSpace as $id => $freeSpace) { - if ($freeSpace > $size) { - return $id; - } - } - - $id = self::$startNode[self::DATA]; - while (TRUE) { - if (isset($this->dataNodeFreeSpace[$id]) || isset($this->nodeCache[$id])) { - ++$id; - continue; - } - - $offset = self::HEADER_SIZE + self::NODE_SIZE * $id; - $binary = stream_get_contents($this->handle, 2 * self::INT32_SIZE, $offset); - - if (empty($binary)) { - $this->dataNodeFreeSpace[$id] = self::NODE_SIZE; - return $id; - } - - list(, $magic, $nodeSize) = unpack('N2', $binary); - if (empty($magic)) { - $this->dataNodeFreeSpace[$id] = self::NODE_SIZE; - return $id; - } elseif ($magic === self::DATA_MAGIC) { - $freeSpace = self::NODE_SIZE - $nodeSize; - $this->dataNodeFreeSpace[$id] = $freeSpace; - - if ($freeSpace > $size) { - return $id; - } - } - - ++$id; - } - } - - - /** - * Bisect node or when has only one key, move part to data node. - * @param int Node ID - * @param array Node - * @return void - */ - private function bisectNode($id, array $node) - { - $nodeInfo = $node[self::INFO]; - unset($node[self::INFO]); - - if (count($node) === 1) { - $key = key($node); - - $dataId = $this->findFreeDataNode(self::NODE_SIZE); - $this->saveNode($dataId, array( - self::INDEX_DATA => $node[$key], - self::INFO => array( - self::TYPE => self::DATA, - self::LAST_INDEX => ($dataId << self::BITROT), - ))); - - unset($node[$key]); - $node[$key][self::INDEX_DATA] = $dataId; - $node[self::INFO] = $nodeInfo; - - $this->saveNode($id, $node); - return; - } - - ksort($node); - $halfCount = ceil(count($node) / 2); - - list($first, $second) = array_chunk($node, $halfCount, TRUE); - - end($first); - $halfKey = key($first); - - if ($id <= 2) { // Root - list($firstId, $secondId) = $this->findFreeNode(2); - - $first[self::INFO] = array( - self::TYPE => $nodeInfo[self::TYPE], - self::IS_LEAF => $nodeInfo[self::IS_LEAF], - self::PREV_NODE => -1, - self::END => -1, - self::MAX => $halfKey, - ); - $this->saveNode($firstId, $first); - - $second[self::INFO] = array( - self::TYPE => $nodeInfo[self::TYPE], - self::IS_LEAF => $nodeInfo[self::IS_LEAF], - self::PREV_NODE => $firstId, - self::END => $nodeInfo[self::END], - self::MAX => -1, - ); - $this->saveNode($secondId, $second); - - $parentNode = array( - self::INFO => array( - self::TYPE => $nodeInfo[self::TYPE], - self::IS_LEAF => FALSE, - self::PREV_NODE => -1, - self::END => $secondId, - self::MAX => -1, - ), - $halfKey => $firstId, - ); - $this->saveNode($id, $parentNode); - } else { - $firstId = $this->findFreeNode(); - - $first[self::INFO] = array( - self::TYPE => $nodeInfo[self::TYPE], - self::IS_LEAF => $nodeInfo[self::IS_LEAF], - self::PREV_NODE => $nodeInfo[self::PREV_NODE], - self::END => -1, - self::MAX => $halfKey, - ); - $this->saveNode($firstId, $first); - - $second[self::INFO] = array( - self::TYPE => $nodeInfo[self::TYPE], - self::IS_LEAF => $nodeInfo[self::IS_LEAF], - self::PREV_NODE => $firstId, - self::END => $nodeInfo[self::END], - self::MAX => $nodeInfo[self::MAX], - ); - $this->saveNode($id, $second); - - list(, , $parent) = $this->findIndexNode($nodeInfo[self::TYPE], $halfKey); - $parentNode = $this->getNode($parent); - if ($parentNode === FALSE) { - if (self::$debug) { - throw new Nette\InvalidStateException("Cannot load node number $parent."); - } - } else { - $parentNode[$halfKey] = $firstId; - ksort($parentNode); // Parent index must be always sorted. - $this->saveNode($parent, $parentNode); - } - } - } - - - /** - * Commit header to journal file. - * @return void - */ - private function headerCommit() - { - fseek($this->handle, self::INT32_SIZE); - @fwrite($this->handle, pack('N', $this->lastNode)); // intentionally @, save is not necessary - } - - - /** - * Remove node from journal file. - * @param int - * @return void - */ - private function deleteNode($id) - { - fseek($this->handle, 0, SEEK_END); - $end = ftell($this->handle); - - if ($end <= (self::HEADER_SIZE + self::NODE_SIZE * ($id + 1))) { - $packedNull = pack('N', 0); - - do { - $binary = stream_get_contents($this->handle, self::INT32_SIZE, (self::HEADER_SIZE + self::NODE_SIZE * --$id)); - } while (empty($binary) || $binary === $packedNull); - - if (!ftruncate($this->handle, self::HEADER_SIZE + self::NODE_SIZE * ($id + 1))) { - throw new Nette\InvalidStateException('Cannot truncate journal file.'); - } - } else { - fseek($this->handle, self::HEADER_SIZE + self::NODE_SIZE * $id); - $written = fwrite($this->handle, pack('N', 0)); - if ($written !== self::INT32_SIZE) { - throw new Nette\InvalidStateException("Cannot delete node number $id from journal."); - } - } - } - - - /** - * Complete delete all nodes from file. - * @throws Nette\InvalidStateException - */ - private function deleteAll() - { - if (!ftruncate($this->handle, self::HEADER_SIZE)) { - throw new Nette\InvalidStateException('Cannot truncate journal file.'); - } - } - - - /** - * Lock file for writing and reading and delete node cache when file has changed. - * @throws Nette\InvalidStateException - */ - private function lock() - { - if (!$this->handle) { - $this->prepare(); - } - - if (!flock($this->handle, LOCK_EX)) { - throw new Nette\InvalidStateException("Cannot acquire exclusive lock on journal file '$this->file'."); - } - - $lastProcessIdentifier = stream_get_contents($this->handle, self::INT32_SIZE, self::INT32_SIZE * 2); - if ($lastProcessIdentifier !== $this->processIdentifier) { - $this->nodeCache = $this->dataNodeFreeSpace = array(); - - // Write current processIdentifier to file header - fseek($this->handle, self::INT32_SIZE * 2); - fwrite($this->handle, $this->processIdentifier); - } - } - - - /** - * Open btfj.dat file (or create it if not exists) and load metainformation. - * @throws Nette\InvalidStateException - */ - private function prepare() - { - // Create journal file when not exists - if (!file_exists($this->file)) { - $init = @fopen($this->file, 'xb'); // intentionally @ - if (!$init) { - clearstatcache(); - if (!file_exists($this->file)) { - throw new Nette\InvalidStateException("Cannot create journal file '$this->file'."); - } - } else { - $written = fwrite($init, pack('N2', self::FILE_MAGIC, $this->lastNode)); - fclose($init); - if ($written !== self::INT32_SIZE * 2) { - throw new Nette\InvalidStateException('Cannot write journal header.'); - } - } - } - - $this->handle = fopen($this->file, 'r+b'); - - if (!$this->handle) { - throw new Nette\InvalidStateException("Cannot open journal file '$this->file'."); - } - - if (!flock($this->handle, LOCK_SH)) { - throw new Nette\InvalidStateException('Cannot acquire shared lock on journal.'); - } - - $header = stream_get_contents($this->handle, 2 * self::INT32_SIZE, 0); - - flock($this->handle, LOCK_UN); - - list(, $fileMagic, $this->lastNode) = unpack('N2', $header); - - if ($fileMagic !== self::FILE_MAGIC) { - fclose($this->handle); - $this->handle = FALSE; - throw new Nette\InvalidStateException("Malformed journal file '$this->file'."); - } - - $this->processIdentifier = pack('N', mt_rand()); - } - - - /** - * Unlock file and save last modified time. - * @return void - */ - private function unlock() - { - if ($this->handle) { - fflush($this->handle); - flock($this->handle, LOCK_UN); - } - } - - - /** - * @param int - * @return int - * @throws Nette\InvalidStateException - */ - private function findNextFreeKey($nodeId, array & $nodeData) - { - $newKey = $nodeData[self::INFO][self::LAST_INDEX] + 1; - $maxKey = ($nodeId + 1) << self::BITROT; - - if ($newKey >= $maxKey) { - $start = $nodeId << self::BITROT; - for ($i = $start; $i < $maxKey; $i++) { - if (!isset($nodeData[$i])) { - return $i; - } - } - throw new Nette\InvalidStateException("Node $nodeId is full."); - } else { - return ++$nodeData[self::INFO][self::LAST_INDEX]; - } - } - - - /** - * Append $append to $array. - * This function is much faster than $array = array_merge($array, $append) - * @param array - * @param array - * @return void - */ - private function arrayAppend(array & $array, array $append) - { - foreach ($append as $value) { - $array[] = $value; - } - } - - - /** - * Append $append to $array with preserve keys. - * This function is much faster than $array = $array + $append - * @param array - * @param array - * @return void - */ - private function arrayAppendKeys(array & $array, array $append) - { - foreach ($append as $key => $value) { - $array[$key] = $value; - } - } - -} diff -Nru php-nette-2.3.10/Nette/Caching/Storages/FileStorage.php php-nette-2.4-20160731/Nette/Caching/Storages/FileStorage.php --- php-nette-2.3.10/Nette/Caching/Storages/FileStorage.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/FileStorage.php 2016-07-31 17:46:32.000000000 +0000 @@ -14,8 +14,10 @@ /** * Cache file storage. */ -class FileStorage extends Nette\Object implements Nette\Caching\IStorage +class FileStorage implements Nette\Caching\IStorage { + use Nette\SmartObject; + /** * Atomic thread safe logic: * @@ -72,7 +74,7 @@ $this->journal = $journal; if (mt_rand() / mt_getrandmax() < static::$gcProbability) { - $this->clean(array()); + $this->clean([]); } } @@ -162,9 +164,9 @@ */ public function write($key, $data, array $dp) { - $meta = array( + $meta = [ self::META_TIME => microtime(), - ); + ]; if (isset($dp[Cache::EXPIRATION])) { if (empty($dp[Cache::SLIDING])) { @@ -215,19 +217,18 @@ $head = serialize($meta) . '?>'; $head = 'prefix . $key); - $meta = array( + $meta = [ self::META_DATA => $data, - ); + ]; $expire = 0; if (isset($dp[Cache::EXPIRATION])) { diff -Nru php-nette-2.3.10/Nette/Caching/Storages/MemoryStorage.php php-nette-2.4-20160731/Nette/Caching/Storages/MemoryStorage.php --- php-nette-2.3.10/Nette/Caching/Storages/MemoryStorage.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/MemoryStorage.php 2016-07-31 17:46:32.000000000 +0000 @@ -13,10 +13,12 @@ /** * Memory cache storage. */ -class MemoryStorage extends Nette\Object implements Nette\Caching\IStorage +class MemoryStorage implements Nette\Caching\IStorage { + use Nette\SmartObject; + /** @var array */ - private $data = array(); + private $data = []; /** @@ -72,7 +74,7 @@ public function clean(array $conditions) { if (!empty($conditions[Nette\Caching\Cache::ALL])) { - $this->data = array(); + $this->data = []; } } diff -Nru php-nette-2.3.10/Nette/Caching/Storages/NewMemcachedStorage.php php-nette-2.4-20160731/Nette/Caching/Storages/NewMemcachedStorage.php --- php-nette-2.3.10/Nette/Caching/Storages/NewMemcachedStorage.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/NewMemcachedStorage.php 2016-07-31 17:46:32.000000000 +0000 @@ -14,8 +14,10 @@ /** * Memcached storage using memcached extension. */ -class NewMemcachedStorage extends Nette\Object implements Nette\Caching\IStorage +class NewMemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader { + use Nette\SmartObject; + /** @internal cache structure */ const META_CALLBACKS = 'callbacks', META_DATA = 'data', @@ -109,6 +111,39 @@ /** + * Reads from cache in bulk. + * @param string key + * @return array key => value pairs, missing items are omitted + */ + public function bulkRead(array $keys) + { + $prefixedKeys = array_map(function ($key) { + return urlencode($this->prefix . $key); + }, $keys); + $keys = array_combine($prefixedKeys, $keys); + $metas = $this->memcached->getMulti($prefixedKeys); + $result = []; + $deleteKeys = []; + foreach ($metas as $prefixedKey => $meta) { + if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) { + $deleteKeys[] = $prefixedKey; + } else { + $result[$keys[$prefixedKey]] = $meta[self::META_DATA]; + } + + if (!empty($meta[self::META_DELTA])) { + $this->memcached->replace($prefixedKey, $meta, $meta[self::META_DELTA] + time()); + } + } + if (!empty($deleteKeys)) { + $this->memcached->deleteMulti($deleteKeys, 0); + } + + return $result; + } + + + /** * Prevents item reading and writing. Lock is released by write() or remove(). * @param string key * @return void @@ -132,9 +167,9 @@ } $key = urlencode($this->prefix . $key); - $meta = array( + $meta = [ self::META_DATA => $data, - ); + ]; $expire = 0; if (isset($dp[Cache::EXPIRATION])) { diff -Nru php-nette-2.3.10/Nette/Caching/Storages/SQLiteJournal.php php-nette-2.4-20160731/Nette/Caching/Storages/SQLiteJournal.php --- php-nette-2.3.10/Nette/Caching/Storages/SQLiteJournal.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/SQLiteJournal.php 2016-07-31 17:46:32.000000000 +0000 @@ -0,0 +1,160 @@ +path = $path; + } + + + private function open() + { + if (!extension_loaded('pdo_sqlite')) { + throw new Nette\NotSupportedException('SQLiteJournal requires PHP extension pdo_sqlite which is not loaded.'); + } + + if ($this->path !== ':memory:' && !is_file($this->path)) { + touch($this->path); // ensures ordinary file permissions + } + + $this->pdo = new \PDO('sqlite:' . $this->path); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->pdo->exec(' + PRAGMA foreign_keys = OFF; + PRAGMA journal_mode = WAL; + CREATE TABLE IF NOT EXISTS tags ( + key BLOB NOT NULL, + tag BLOB NOT NULL + ); + CREATE TABLE IF NOT EXISTS priorities ( + key BLOB NOT NULL, + priority INT NOT NULL + ); + CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag); + CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_key_tag ON tags(key, tag); + CREATE UNIQUE INDEX IF NOT EXISTS idx_priorities_key ON priorities(key); + CREATE INDEX IF NOT EXISTS idx_priorities_priority ON priorities(priority); + '); + } + + + /** + * Writes entry information into the journal. + * @param string + * @param array + * @return bool + */ + public function write($key, array $dependencies) + { + if (!$this->pdo) { + $this->open(); + } + $this->pdo->exec('BEGIN'); + + if (!empty($dependencies[Cache::TAGS])) { + $this->pdo->prepare('DELETE FROM tags WHERE key = ?')->execute([$key]); + + foreach ((array) $dependencies[Cache::TAGS] as $tag) { + $arr[] = $key; + $arr[] = $tag; + } + $this->pdo->prepare('INSERT INTO tags (key, tag) SELECT ?, ?' . str_repeat('UNION SELECT ?, ?', count($arr) / 2 - 1)) + ->execute($arr); + } + + if (!empty($dependencies[Cache::PRIORITY])) { + $this->pdo->prepare('REPLACE INTO priorities (key, priority) VALUES (?, ?)') + ->execute([$key, (int) $dependencies[Cache::PRIORITY]]); + } + + $this->pdo->exec('COMMIT'); + + return TRUE; + } + + + /** + * Cleans entries from journal. + * @param array + * @return array|NULL removed items or NULL when performing a full cleanup + */ + public function clean(array $conditions) + { + if (!$this->pdo) { + $this->open(); + } + if (!empty($conditions[Cache::ALL])) { + $this->pdo->exec(' + BEGIN; + DELETE FROM tags; + DELETE FROM priorities; + COMMIT; + '); + + return NULL; + } + + $unions = $args = []; + if (!empty($conditions[Cache::TAGS])) { + $tags = (array) $conditions[Cache::TAGS]; + $unions[] = 'SELECT DISTINCT key FROM tags WHERE tag IN (?' . str_repeat(', ?', count($tags) - 1) . ')'; + $args = $tags; + } + + if (!empty($conditions[Cache::PRIORITY])) { + $unions[] = 'SELECT DISTINCT key FROM priorities WHERE priority <= ?'; + $args[] = (int) $conditions[Cache::PRIORITY]; + } + + if (empty($unions)) { + return []; + } + + $unionSql = implode(' UNION ', $unions); + + $this->pdo->exec('BEGIN IMMEDIATE'); + + $stmt = $this->pdo->prepare($unionSql); + $stmt->execute($args); + $keys = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0); + + if (empty($keys)) { + $this->pdo->exec('COMMIT'); + return []; + } + + $this->pdo->prepare("DELETE FROM tags WHERE key IN ($unionSql)")->execute($args); + $this->pdo->prepare("DELETE FROM priorities WHERE key IN ($unionSql)")->execute($args); + $this->pdo->exec('COMMIT'); + + return $keys; + } + +} diff -Nru php-nette-2.3.10/Nette/Caching/Storages/SQLiteStorage.php php-nette-2.4-20160731/Nette/Caching/Storages/SQLiteStorage.php --- php-nette-2.3.10/Nette/Caching/Storages/SQLiteStorage.php 2016-04-13 18:50:40.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Caching/Storages/SQLiteStorage.php 2016-07-31 17:46:32.000000000 +0000 @@ -14,28 +14,34 @@ /** * SQLite storage. */ -class SQLiteStorage extends Nette\Object implements Nette\Caching\IStorage +class SQLiteStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader { + use Nette\SmartObject; + /** @var \PDO */ private $pdo; - public function __construct($path = ':memory:') + public function __construct($path) { - $this->pdo = new \PDO('sqlite:' . $path, NULL, NULL, array(\PDO::ATTR_PERSISTENT => TRUE)); + $this->pdo = new \PDO('sqlite:' . $path); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->pdo->exec(' PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS cache ( key BLOB NOT NULL PRIMARY KEY, - data BLOB NOT NULL + data BLOB NOT NULL, + expire INTEGER, + slide INTEGER ); CREATE TABLE IF NOT EXISTS tags ( key BLOB NOT NULL REFERENCES cache ON DELETE CASCADE, tag BLOB NOT NULL ); + CREATE INDEX IF NOT EXISTS cache_expire ON cache(expire); CREATE INDEX IF NOT EXISTS tags_key ON tags(key); CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag); + PRAGMA synchronous = OFF; '); } @@ -47,15 +53,43 @@ */ public function read($key) { - $stmt = $this->pdo->prepare('SELECT data FROM cache WHERE key=?'); - $stmt->execute(array($key)); - if ($res = $stmt->fetchColumn()) { - return unserialize($res); + $stmt = $this->pdo->prepare('SELECT data, slide FROM cache WHERE key=? AND (expire IS NULL OR expire >= ?)'); + $stmt->execute([$key, time()]); + if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if ($row['slide'] !== NULL) { + $this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key=?')->execute([time(), $key]); + } + return unserialize($row['data']); } } /** + * Reads from cache in bulk. + * @param string key + * @return array key => value pairs, missing items are omitted + */ + public function bulkRead(array $keys) + { + $stmt = $this->pdo->prepare('SELECT key, data, slide FROM cache WHERE key IN (?' . str_repeat(',?', count($keys) - 1) . ') AND (expire IS NULL OR expire >= ?)'); + $stmt->execute(array_merge($keys, [time()])); + $result = []; + $updateSlide = []; + foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + if ($row['slide'] !== NULL) { + $updateSlide[] = $row['key']; + } + $result[$row['key']] = unserialize($row['data']); + } + if (!empty($updateSlide)) { + $stmt = $this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key IN(?' . str_repeat(',?', count($updateSlide) - 1) . ')'); + $stmt->execute(array_merge([time()], $updateSlide)); + } + return $result; + } + + + /** * Prevents item reading and writing. Lock is released by write() or remove(). * @param string key * @return void @@ -74,9 +108,12 @@ */ public function write($key, $data, array $dependencies) { + $expire = isset($dependencies[Cache::EXPIRATION]) ? $dependencies[Cache::EXPIRATION] + time() : NULL; + $slide = isset($dependencies[Cache::SLIDING]) ? $dependencies[Cache::EXPIRATION] : NULL; + $this->pdo->exec('BEGIN TRANSACTION'); - $this->pdo->prepare('REPLACE INTO cache (key, data) VALUES (?, ?)') - ->execute(array($key, serialize($data))); + $this->pdo->prepare('REPLACE INTO cache (key, data, expire, slide) VALUES (?, ?, ?, ?)') + ->execute([$key, serialize($data), $expire, $slide]); if (!empty($dependencies[Cache::TAGS])) { foreach ((array) $dependencies[Cache::TAGS] as $tag) { @@ -98,7 +135,7 @@ public function remove($key) { $this->pdo->prepare('DELETE FROM cache WHERE key=?') - ->execute(array($key)); + ->execute([$key]); } @@ -112,10 +149,17 @@ if (!empty($conditions[Cache::ALL])) { $this->pdo->prepare('DELETE FROM cache')->execute(); - } elseif (!empty($conditions[Cache::TAGS])) { - $tags = (array) $conditions[Cache::TAGS]; - $this->pdo->prepare('DELETE FROM cache WHERE key IN (SELECT key FROM tags WHERE tag IN (?' - . str_repeat(',?', count($tags) - 1) . '))')->execute($tags); + } else { + $sql = 'DELETE FROM cache WHERE expire < ?'; + $args = [time()]; + + if (!empty($conditions[Cache::TAGS])) { + $tags = (array) $conditions[Cache::TAGS]; + $sql .= ' OR key IN (SELECT key FROM tags WHERE tag IN (?' . str_repeat(',?', count($tags) - 1) . '))'; + $args = array_merge($args, $tags); + } + + $this->pdo->prepare($sql)->execute($args); } } diff -Nru php-nette-2.3.10/Nette/compatibility.php php-nette-2.4-20160731/Nette/compatibility.php --- php-nette-2.3.10/Nette/compatibility.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/compatibility.php 2016-07-31 17:46:32.000000000 +0000 @@ -0,0 +1,9 @@ + [obj, depth, path, is_monitored?]] */ - private $monitors = array(); + private $monitors = []; - public function __construct(IContainer $parent = NULL, $name = NULL) + public function __construct() { + list($parent, $name) = func_get_args() + [NULL, NULL]; if ($parent !== NULL) { $parent->addComponent($this, $name); @@ -67,10 +70,10 @@ } if ($obj) { - $this->monitors[$type] = array($obj, $depth, substr($path, 1), FALSE); + $this->monitors[$type] = [$obj, $depth, substr($path, 1), FALSE]; } else { - $this->monitors[$type] = array(NULL, NULL, NULL, FALSE); // not found + $this->monitors[$type] = [NULL, NULL, NULL, FALSE]; // not found } } @@ -203,7 +206,7 @@ $this->name = $name; } - $tmp = array(); + $tmp = []; $this->refreshMonitors(0, $tmp); } return $this; @@ -228,7 +231,7 @@ * @param array * @return void */ - private function refreshMonitors($depth, & $missing = NULL, & $listeners = array()) + private function refreshMonitors($depth, & $missing = NULL, & $listeners = []) { if ($this instanceof IContainer) { foreach ($this->getComponents() as $component) { @@ -242,8 +245,8 @@ foreach ($this->monitors as $type => $rec) { if (isset($rec[1]) && $rec[1] > $depth) { if ($rec[3]) { // monitored - $this->monitors[$type] = array(NULL, NULL, NULL, TRUE); - $listeners[] = array($this, $rec[0]); + $this->monitors[$type] = [NULL, NULL, NULL, TRUE]; + $listeners[] = [$this, $rec[0]]; } else { // not monitored, just randomly cached unset($this->monitors[$type]); } @@ -259,12 +262,12 @@ unset($this->monitors[$type]); } elseif (isset($missing[$type])) { // known from previous lookup - $this->monitors[$type] = array(NULL, NULL, NULL, TRUE); + $this->monitors[$type] = [NULL, NULL, NULL, TRUE]; } else { $this->monitors[$type] = NULL; // forces re-lookup if ($obj = $this->lookup($type, FALSE)) { - $listeners[] = array($this, $obj); + $listeners[] = [$this, $obj]; } else { $missing[$type] = TRUE; } @@ -275,7 +278,7 @@ if ($depth === 0) { // call listeners $method = $missing === NULL ? 'detached' : 'attached'; - $prev = array(); + $prev = []; foreach ($listeners as $item) { if (!in_array($item, $prev, TRUE)) { $item[0]->$method($item[1]); diff -Nru php-nette-2.3.10/Nette/ComponentModel/Container.php php-nette-2.4-20160731/Nette/ComponentModel/Container.php --- php-nette-2.3.10/Nette/ComponentModel/Container.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/ComponentModel/Container.php 2016-07-31 17:46:34.000000000 +0000 @@ -18,7 +18,7 @@ class Container extends Component implements IContainer { /** @var IComponent[] */ - private $components = array(); + private $components = []; /** @var IComponent|NULL */ private $cloning; @@ -69,7 +69,7 @@ try { if (isset($this->components[$insertBefore])) { - $tmp = array(); + $tmp = []; foreach ($this->components as $k => $v) { if ($k === $insertBefore) { $tmp[$name] = $component; @@ -114,6 +114,10 @@ */ public function getComponent($name, $need = TRUE) { + if (isset($this->components[$name])) { + return $this->components[$name]; + } + if (is_int($name)) { $name = (string) $name; @@ -177,7 +181,7 @@ { $ucname = ucfirst($name); $method = 'createComponent' . $ucname; - if ($ucname !== $name && method_exists($this, $method) && $this->getReflection()->getMethod($method)->getName() === $method) { + if ($ucname !== $name && method_exists($this, $method) && (new \ReflectionMethod($this, $method))->getName() === $method) { $component = $this->$method($name); if (!$component instanceof IComponent && !isset($this->components[$name])) { $class = get_class($this); @@ -202,8 +206,7 @@ $iterator = new \RecursiveIteratorIterator($iterator, $deep); } if ($filterType) { - $class = PHP_VERSION_ID < 50400 ? 'Nette\Iterators\Filter' : 'CallbackFilterIterator'; - $iterator = new $class($iterator, function ($item) use ($filterType) { + $iterator = new \CallbackFilterIterator($iterator, function ($item) use ($filterType) { return $item instanceof $filterType; }); } diff -Nru php-nette-2.3.10/Nette/Database/Connection.php php-nette-2.4-20160731/Nette/Database/Connection.php --- php-nette-2.3.10/Nette/Database/Connection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Connection.php 2016-07-31 17:46:34.000000000 +0000 @@ -14,13 +14,11 @@ /** * Represents a connection between PHP and a database server. - * - * @property-read ISupplementalDriver $supplementalDriver - * @property-read string $dsn - * @property-read PDO $pdo */ -class Connection extends Nette\Object +class Connection { + use Nette\SmartObject; + /** @var callable[] function (Connection $connection); Occurs after connection is established */ public $onConnect; @@ -46,9 +44,10 @@ public function __construct($dsn, $user = NULL, $password = NULL, array $options = NULL) { if (func_num_args() > 4) { // compatibility + trigger_error(__METHOD__ . " fifth argument is deprecated, use \$options['driverClass'].", E_USER_DEPRECATED); $options['driverClass'] = func_get_arg(4); } - $this->params = array($dsn, $user, $password); + $this->params = [$dsn, $user, $password]; $this->options = (array) $options; if (empty($options['lazy'])) { @@ -171,18 +170,11 @@ /** * Generates and executes SQL query. * @param string - * @param mixed [parameters, ...] * @return ResultSet */ - public function query($sql) + public function query($sql, ...$params) { - $this->connect(); - - $args = is_array($sql) ? $sql : func_get_args(); // accepts arrays only internally - list($sql, $params) = count($args) > 1 - ? $this->preprocessor->process($args) - : array($args[0], array()); - + list($sql, $params) = $this->preprocess($sql, ...$params); try { $result = new ResultSet($this, $sql, $params); } catch (PDOException $e) { @@ -200,20 +192,19 @@ */ public function queryArgs($sql, array $params) { - array_unshift($params, $sql); - return $this->query($params); + return $this->query($sql, ...$params); } /** * @return [string, array] */ - public function preprocess($sql) + public function preprocess($sql, ...$params) { $this->connect(); - return func_num_args() > 1 + return $params ? $this->preprocessor->process(func_get_args()) - : array($sql, array()); + : [$sql, []]; } @@ -223,58 +214,52 @@ /** * Shortcut for query()->fetch() * @param string - * @param mixed [parameters, ...] * @return Row */ - public function fetch($args) + public function fetch($sql, ...$params) { - return $this->query(func_get_args())->fetch(); + return $this->query($sql, ...$params)->fetch(); } /** * Shortcut for query()->fetchField() * @param string - * @param mixed [parameters, ...] * @return mixed */ - public function fetchField($args) + public function fetchField($sql, ...$params) { - return $this->query(func_get_args())->fetchField(); + return $this->query($sql, ...$params)->fetchField(); } /** * Shortcut for query()->fetchPairs() * @param string - * @param mixed [parameters, ...] * @return array */ - public function fetchPairs($args) + public function fetchPairs($sql, ...$params) { - return $this->query(func_get_args())->fetchPairs(); + return $this->query($sql, ...$params)->fetchPairs(); } /** * Shortcut for query()->fetchAll() * @param string - * @param mixed [parameters, ...] * @return array */ - public function fetchAll($args) + public function fetchAll($sql, ...$params) { - return $this->query(func_get_args())->fetchAll(); + return $this->query($sql, ...$params)->fetchAll(); } /** * @return SqlLiteral */ - public static function literal($value) + public static function literal($value, ...$params) { - $args = func_get_args(); - return new SqlLiteral(array_shift($args), $args); + return new SqlLiteral($value, $params); } - } diff -Nru php-nette-2.3.10/Nette/Database/Context.php php-nette-2.4-20160731/Nette/Database/Context.php --- php-nette-2.3.10/Nette/Database/Context.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Context.php 2016-07-31 17:46:34.000000000 +0000 @@ -14,8 +14,10 @@ /** * Database context. */ -class Context extends Nette\Object +class Context { + use Nette\SmartObject; + /** @var Connection */ private $connection; @@ -72,12 +74,11 @@ /** * Generates and executes SQL query. * @param string - * @param mixed [parameters, ...] * @return ResultSet */ - public function query($sql) + public function query($sql, ...$params) { - return $this->connection->query(func_get_args()); + return $this->connection->query($sql, ...$params); } @@ -87,7 +88,7 @@ */ public function queryArgs($sql, array $params) { - return $this->connection->queryArgs($sql, $params); + return $this->connection->query($sql, ...$params); } @@ -122,72 +123,59 @@ } - /** @deprecated */ - public function getDatabaseReflection() - { - trigger_error(__METHOD__ . '() is deprecated; use getConventions() instead.', E_USER_DEPRECATED); - return $this->conventions; - } - - /********************* shortcuts ****************d*g**/ /** * Shortcut for query()->fetch() * @param string - * @param mixed [parameters, ...] * @return Row */ - public function fetch($args) + public function fetch($sql, ...$params) { - return $this->connection->query(func_get_args())->fetch(); + return $this->connection->query($sql, ...$params)->fetch(); } /** * Shortcut for query()->fetchField() * @param string - * @param mixed [parameters, ...] * @return mixed */ - public function fetchField($args) + public function fetchField($sql, ...$params) { - return $this->connection->query(func_get_args())->fetchField(); + return $this->connection->query($sql, ...$params)->fetchField(); } /** * Shortcut for query()->fetchPairs() * @param string - * @param mixed [parameters, ...] * @return array */ - public function fetchPairs($args) + public function fetchPairs($sql, ...$params) { - return $this->connection->query(func_get_args())->fetchPairs(); + return $this->connection->query($sql, ...$params)->fetchPairs(); } /** * Shortcut for query()->fetchAll() * @param string - * @param mixed [parameters, ...] * @return array */ - public function fetchAll($args) + public function fetchAll($sql, ...$params) { - return $this->connection->query(func_get_args())->fetchAll(); + return $this->connection->query($sql, ...$params)->fetchAll(); } /** * @return SqlLiteral */ - public static function literal($value) + public static function literal($value, ...$params) { - $args = func_get_args(); - return new SqlLiteral(array_shift($args), $args); + return new SqlLiteral($value, $params); } } diff -Nru php-nette-2.3.10/Nette/Database/Conventions/DiscoveredConventions.php php-nette-2.4-20160731/Nette/Database/Conventions/DiscoveredConventions.php --- php-nette-2.3.10/Nette/Database/Conventions/DiscoveredConventions.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Conventions/DiscoveredConventions.php 2016-07-31 17:46:34.000000000 +0000 @@ -34,7 +34,7 @@ public function getHasManyReference($nsTable, $key) { - $candidates = $columnCandidates = array(); + $candidates = $columnCandidates = []; $targets = $this->structure->getHasManyReference($nsTable); $table = preg_replace('#^(.*\.)?(.*)$#', '$2', $nsTable); @@ -46,13 +46,13 @@ foreach ($targetColumns as $targetColumn) { if (stripos($targetColumn, $table) !== FALSE) { - $columnCandidates[] = $candidate = array($targetNsTable, $targetColumn); + $columnCandidates[] = $candidate = [$targetNsTable, $targetColumn]; if (strcmp($targetTable, $key) === 0 || strcmp($targetNsTable, $key) === 0) { return $candidate; } } - $candidates[] = array($targetTable, array($targetNsTable, $targetColumn)); + $candidates[] = [$targetTable, [$targetNsTable, $targetColumn]]; } } @@ -87,7 +87,7 @@ foreach ($tableColumns as $column => $targetTable) { if (stripos($column, $key) !== FALSE) { - return array($targetTable, $column); + return [$targetTable, $column]; } } diff -Nru php-nette-2.3.10/Nette/Database/Conventions/StaticConventions.php php-nette-2.4-20160731/Nette/Database/Conventions/StaticConventions.php --- php-nette-2.3.10/Nette/Database/Conventions/StaticConventions.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Conventions/StaticConventions.php 2016-07-31 17:46:34.000000000 +0000 @@ -7,15 +7,17 @@ namespace Nette\Database\Conventions; +use Nette; use Nette\Database\IConventions; -use Nette\Object; /** * Conventions based on static definition. */ -class StaticConventions extends Object implements IConventions +class StaticConventions implements IConventions { + use Nette\SmartObject; + /** @var string */ protected $primary; @@ -49,20 +51,20 @@ public function getHasManyReference($table, $key) { $table = $this->getColumnFromTable($table); - return array( + return [ sprintf($this->table, $key, $table), sprintf($this->foreign, $table, $key), - ); + ]; } public function getBelongsToReference($table, $key) { $table = $this->getColumnFromTable($table); - return array( + return [ sprintf($this->table, $key, $table), sprintf($this->foreign, $key, $table), - ); + ]; } diff -Nru php-nette-2.3.10/Nette/Database/deprecated/ConventionalReflection.php php-nette-2.4-20160731/Nette/Database/deprecated/ConventionalReflection.php --- php-nette-2.3.10/Nette/Database/deprecated/ConventionalReflection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/deprecated/ConventionalReflection.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -connection = $connection; - if ($cacheStorage) { - $this->cache = new Nette\Caching\Cache($cacheStorage, 'Nette.Database.' . md5($connection->getDsn())); - $this->structure = $this->loadedStructure = $this->cache->load('structure') ?: array(); - } - } - - - public function __destruct() - { - if ($this->cache && $this->structure !== $this->loadedStructure) { - $this->cache->save('structure', $this->structure); - } - } - - - public function getPrimary($table) - { - $primary = & $this->structure['primary'][strtolower($table)]; - if (isset($primary)) { - return empty($primary) ? NULL : $primary; - } - - $columns = $this->connection->getSupplementalDriver()->getColumns($table); - $primary = array(); - foreach ($columns as $column) { - if ($column['primary']) { - $primary[] = $column['name']; - } - } - - if (count($primary) === 0) { - return NULL; - } elseif (count($primary) === 1) { - $primary = reset($primary); - } - - return $primary; - } - - - public function getHasManyReference($table, $key, $refresh = TRUE) - { - if (isset($this->structure['hasMany'][strtolower($table)])) { - $candidates = $columnCandidates = array(); - foreach ($this->structure['hasMany'][strtolower($table)] as $targetPair) { - list($targetColumn, $targetTable) = $targetPair; - if (stripos($targetTable, $key) === FALSE) { - continue; - } - - $candidates[] = array($targetTable, $targetColumn); - if (stripos($targetColumn, $table) !== FALSE) { - $columnCandidates[] = $candidate = array($targetTable, $targetColumn); - if (strtolower($targetTable) === strtolower($key)) { - return $candidate; - } - } - } - - if (count($columnCandidates) === 1) { - return reset($columnCandidates); - } elseif (count($candidates) === 1) { - return reset($candidates); - } - - foreach ($candidates as $candidate) { - if (strtolower($candidate[0]) === strtolower($key)) { - return $candidate; - } - } - } - - if ($refresh) { - $this->reloadAllForeignKeys(); - return $this->getHasManyReference($table, $key, FALSE); - } - - if (empty($candidates)) { - throw new MissingReferenceException("No reference found for \${$table}->related({$key})."); - } else { - throw new AmbiguousReferenceKeyException('Ambiguous joining column in related call.'); - } - } - - - public function getBelongsToReference($table, $key, $refresh = TRUE) - { - if (isset($this->structure['belongsTo'][strtolower($table)])) { - foreach ($this->structure['belongsTo'][strtolower($table)] as $column => $targetTable) { - if (stripos($column, $key) !== FALSE) { - return array($targetTable, $column); - } - } - } - - if ($refresh) { - $this->reloadForeignKeys($table); - return $this->getBelongsToReference($table, $key, FALSE); - } - - throw new MissingReferenceException("No reference found for \${$table}->{$key}."); - } - - - protected function reloadAllForeignKeys() - { - $this->structure['hasMany'] = $this->structure['belongsTo'] = array(); - - foreach ($this->connection->getSupplementalDriver()->getTables() as $table) { - if ($table['view'] == FALSE) { - $this->reloadForeignKeys($table['name']); - } - } - - foreach ($this->structure['hasMany'] as & $table) { - uksort($table, function ($a, $b) { - return strlen($a) - strlen($b); - }); - } - } - - - protected function reloadForeignKeys($table) - { - foreach ($this->connection->getSupplementalDriver()->getForeignKeys($table) as $row) { - $this->structure['belongsTo'][strtolower($table)][$row['local']] = $row['table']; - $this->structure['hasMany'][strtolower($row['table'])][$row['local'] . $table] = array($row['local'], $table); - } - - if (isset($this->structure['belongsTo'][$table])) { - uksort($this->structure['belongsTo'][$table], function ($a, $b) { - return strlen($a) - strlen($b); - }); - } - } - -} diff -Nru php-nette-2.3.10/Nette/Database/deprecated/exceptions.php php-nette-2.4-20160731/Nette/Database/deprecated/exceptions.php --- php-nette-2.3.10/Nette/Database/deprecated/exceptions.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/deprecated/exceptions.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ - "''", '%' => '[%]', '_' => '[_]', '[' => '[[]')); + $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); } diff -Nru php-nette-2.3.10/Nette/Database/Drivers/MySqlDriver.php php-nette-2.4-20160731/Nette/Database/Drivers/MySqlDriver.php --- php-nette-2.3.10/Nette/Database/Drivers/MySqlDriver.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Drivers/MySqlDriver.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Supplemental MySQL database driver. */ -class MySqlDriver extends Nette\Object implements Nette\Database\ISupplementalDriver +class MySqlDriver implements Nette\Database\ISupplementalDriver { + use Nette\SmartObject; + const ERROR_ACCESS_DENIED = 1045; const ERROR_DUPLICATE_ENTRY = 1062; const ERROR_DATA_TRUNCATED = 1265; @@ -49,16 +51,16 @@ public function convertException(\PDOException $e) { $code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : NULL; - if (in_array($code, array(1216, 1217, 1451, 1452, 1701), TRUE)) { + if (in_array($code, [1216, 1217, 1451, 1452, 1701], TRUE)) { return Nette\Database\ForeignKeyConstraintViolationException::from($e); - } elseif (in_array($code, array(1062, 1557, 1569, 1586), TRUE)) { + } elseif (in_array($code, [1062, 1557, 1569, 1586], TRUE)) { return Nette\Database\UniqueConstraintViolationException::from($e); } elseif ($code >= 2001 && $code <= 2028) { return Nette\Database\ConnectionException::from($e); - } elseif (in_array($code, array(1048, 1121, 1138, 1171, 1252, 1263, 1566), TRUE)) { + } elseif (in_array($code, [1048, 1121, 1138, 1171, 1252, 1263, 1566], TRUE)) { return Nette\Database\NotNullConstraintViolationException::from($e); } else { @@ -150,12 +152,12 @@ */ public function getTables() { - $tables = array(); + $tables = []; foreach ($this->connection->query('SHOW FULL TABLES') as $row) { - $tables[] = array( + $tables[] = [ 'name' => $row[0], 'view' => isset($row[1]) && $row[1] === 'VIEW', - ); + ]; } return $tables; } @@ -166,10 +168,10 @@ */ public function getColumns($table) { - $columns = array(); + $columns = []; foreach ($this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimite($table)) as $row) { $type = explode('(', $row['Type']); - $columns[] = array( + $columns[] = [ 'name' => $row['Field'], 'table' => $table, 'nativetype' => strtoupper($type[0]), @@ -180,7 +182,7 @@ 'autoincrement' => $row['Extra'] === 'auto_increment', 'primary' => $row['Key'] === 'PRI', 'vendor' => (array) $row, - ); + ]; } return $columns; } @@ -191,7 +193,7 @@ */ public function getIndexes($table) { - $indexes = array(); + $indexes = []; foreach ($this->connection->query('SHOW INDEX FROM ' . $this->delimite($table)) as $row) { $indexes[$row['Key_name']]['name'] = $row['Key_name']; $indexes[$row['Key_name']]['unique'] = !$row['Non_unique']; @@ -207,7 +209,7 @@ */ public function getForeignKeys($table) { - $keys = array(); + $keys = []; $query = 'SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE ' . 'WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_NAME = ' . $this->connection->quote($table); @@ -227,7 +229,7 @@ */ public function getColumnTypes(\PDOStatement $statement) { - $types = array(); + $types = []; $count = $statement->columnCount(); for ($col = 0; $col < $count; $col++) { $meta = $statement->getColumnMeta($col); diff -Nru php-nette-2.3.10/Nette/Database/Drivers/OciDriver.php php-nette-2.4-20160731/Nette/Database/Drivers/OciDriver.php --- php-nette-2.3.10/Nette/Database/Drivers/OciDriver.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Drivers/OciDriver.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Supplemental Oracle database driver. */ -class OciDriver extends Nette\Object implements Nette\Database\ISupplementalDriver +class OciDriver implements Nette\Database\ISupplementalDriver { + use Nette\SmartObject; + /** @var Nette\Database\Connection */ private $connection; @@ -32,13 +34,13 @@ public function convertException(\PDOException $e) { $code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : NULL; - if (in_array($code, array(1, 2299, 38911), TRUE)) { + if (in_array($code, [1, 2299, 38911], TRUE)) { return Nette\Database\UniqueConstraintViolationException::from($e); - } elseif (in_array($code, array(1400), TRUE)) { + } elseif (in_array($code, [1400], TRUE)) { return Nette\Database\NotNullConstraintViolationException::from($e); - } elseif (in_array($code, array(2266, 2291, 2292), TRUE)) { + } elseif (in_array($code, [2266, 2291, 2292], TRUE)) { return Nette\Database\ForeignKeyConstraintViolationException::from($e); } else { @@ -133,13 +135,13 @@ */ public function getTables() { - $tables = array(); + $tables = []; foreach ($this->connection->query('SELECT * FROM cat') as $row) { if ($row[1] === 'TABLE' || $row[1] === 'VIEW') { - $tables[] = array( + $tables[] = [ 'name' => $row[0], 'view' => $row[1] === 'VIEW', - ); + ]; } } return $tables; diff -Nru php-nette-2.3.10/Nette/Database/Drivers/OdbcDriver.php php-nette-2.4-20160731/Nette/Database/Drivers/OdbcDriver.php --- php-nette-2.3.10/Nette/Database/Drivers/OdbcDriver.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Drivers/OdbcDriver.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,9 @@ /** * Supplemental ODBC database driver. */ -class OdbcDriver extends Nette\Object implements Nette\Database\ISupplementalDriver +class OdbcDriver implements Nette\Database\ISupplementalDriver { + use Nette\SmartObject; public function convertException(\PDOException $e) { @@ -30,7 +31,7 @@ */ public function delimite($name) { - return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']'; + return '[' . str_replace(['[', ']'], ['[[', ']]'], $name) . ']'; } @@ -66,7 +67,7 @@ */ public function formatLike($value, $pos) { - $value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]')); + $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); } diff -Nru php-nette-2.3.10/Nette/Database/Drivers/PgSqlDriver.php php-nette-2.4-20160731/Nette/Database/Drivers/PgSqlDriver.php --- php-nette-2.3.10/Nette/Database/Drivers/PgSqlDriver.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Drivers/PgSqlDriver.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Supplemental PostgreSQL database driver. */ -class PgSqlDriver extends Nette\Object implements Nette\Database\ISupplementalDriver +class PgSqlDriver implements Nette\Database\ISupplementalDriver { + use Nette\SmartObject; + /** @var Nette\Database\Connection */ private $connection; @@ -96,7 +98,7 @@ { $bs = substr($this->connection->quote('\\', \PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off $value = substr($this->connection->quote($value, \PDO::PARAM_STR), 1, -1); - $value = strtr($value, array('%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\')); + $value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); } @@ -135,17 +137,17 @@ */ public function getTables() { - $tables = array(); + $tables = []; foreach ($this->connection->query(" SELECT DISTINCT ON (c.relname) c.relname::varchar AS name, - c.relkind = 'v' AS view, + c.relkind IN ('v', 'm') AS view, n.nspname::varchar || '.' || c.relname::varchar AS \"fullName\" FROM pg_catalog.pg_class AS c JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace WHERE - c.relkind IN ('r', 'v') + c.relkind IN ('r', 'v', 'm') AND n.nspname = ANY (pg_catalog.current_schemas(FALSE)) ORDER BY c.relname @@ -162,7 +164,7 @@ */ public function getColumns($table) { - $columns = array(); + $columns = []; foreach ($this->connection->query(" SELECT a.attname::varchar AS name, @@ -205,7 +207,7 @@ */ public function getIndexes($table) { - $indexes = array(); + $indexes = []; foreach ($this->connection->query(" SELECT c2.relname::varchar AS name, @@ -284,7 +286,7 @@ */ private function delimiteFQN($name) { - return implode('.', array_map(array($this, 'delimite'), explode('.', $name))); + return implode('.', array_map([$this, 'delimite'], explode('.', $name))); } } diff -Nru php-nette-2.3.10/Nette/Database/Drivers/SqliteDriver.php php-nette-2.4-20160731/Nette/Database/Drivers/SqliteDriver.php --- php-nette-2.3.10/Nette/Database/Drivers/SqliteDriver.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Drivers/SqliteDriver.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Supplemental SQLite3 database driver. */ -class SqliteDriver extends Nette\Object implements Nette\Database\ISupplementalDriver +class SqliteDriver implements Nette\Database\ISupplementalDriver { + use Nette\SmartObject; + /** @var Nette\Database\Connection */ private $connection; @@ -146,17 +148,17 @@ */ public function getTables() { - $tables = array(); + $tables = []; foreach ($this->connection->query(" SELECT name, type = 'view' as view FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' UNION ALL SELECT name, type = 'view' as view FROM sqlite_temp_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name ") as $row) { - $tables[] = array( + $tables[] = [ 'name' => $row->name, 'view' => (bool) $row->view, - ); + ]; } return $tables; @@ -174,12 +176,12 @@ SELECT sql FROM sqlite_temp_master WHERE type = 'table' AND name = {$this->connection->quote($table)} ")->fetch(); - $columns = array(); + $columns = []; foreach ($this->connection->query("PRAGMA table_info({$this->delimite($table)})") as $row) { $column = $row['name']; $pattern = "/(\"$column\"|\[$column\]|$column)\\s+[^,]+\\s+PRIMARY\\s+KEY\\s+AUTOINCREMENT/Ui"; $type = explode('(', $row['type']); - $columns[] = array( + $columns[] = [ 'name' => $column, 'table' => $table, 'nativetype' => strtoupper($type[0]), @@ -190,7 +192,7 @@ 'autoincrement' => (bool) preg_match($pattern, $meta['sql']), 'primary' => $row['pk'] > 0, 'vendor' => (array) $row, - ); + ]; } return $columns; } @@ -201,7 +203,7 @@ */ public function getIndexes($table) { - $indexes = array(); + $indexes = []; foreach ($this->connection->query("PRAGMA index_list({$this->delimite($table)})") as $row) { $indexes[$row['name']]['name'] = $row['name']; $indexes[$row['name']]['unique'] = (bool) $row['unique']; @@ -228,12 +230,12 @@ if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid foreach ($columns as $column) { if ($column['vendor']['pk']) { - $indexes[] = array( + $indexes[] = [ 'name' => 'ROWID', 'unique' => TRUE, 'primary' => TRUE, - 'columns' => array($column['name']), - ); + 'columns' => [$column['name']], + ]; break; } } @@ -248,7 +250,7 @@ */ public function getForeignKeys($table) { - $keys = array(); + $keys = []; foreach ($this->connection->query("PRAGMA foreign_key_list({$this->delimite($table)})") as $row) { $keys[$row['id']]['name'] = $row['id']; // foreign key name $keys[$row['id']]['local'] = $row['from']; // local columns @@ -270,7 +272,7 @@ */ public function getColumnTypes(\PDOStatement $statement) { - $types = array(); + $types = []; $count = $statement->columnCount(); for ($col = 0; $col < $count; $col++) { $meta = $statement->getColumnMeta($col); diff -Nru php-nette-2.3.10/Nette/Database/Drivers/SqlsrvDriver.php php-nette-2.4-20160731/Nette/Database/Drivers/SqlsrvDriver.php --- php-nette-2.3.10/Nette/Database/Drivers/SqlsrvDriver.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Drivers/SqlsrvDriver.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Supplemental SQL Server 2005 and later database driver. */ -class SqlsrvDriver extends Nette\Object implements Nette\Database\ISupplementalDriver +class SqlsrvDriver implements Nette\Database\ISupplementalDriver { + use Nette\SmartObject; + /** @var Nette\Database\Connection */ private $connection; @@ -63,7 +65,7 @@ public function formatDateTime(/*\DateTimeInterface*/ $value) { /** @see https://msdn.microsoft.com/en-us/library/ms187819.aspx */ - return $value->format("'Y-m-d H:i:s'"); + return $value->format("'Y-m-d\\TH:i:s'"); } @@ -82,7 +84,7 @@ public function formatLike($value, $pos) { /** @see https://msdn.microsoft.com/en-us/library/ms179859.aspx */ - $value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]')); + $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); } @@ -131,7 +133,7 @@ */ public function getTables() { - $tables = array(); + $tables = []; foreach ($this->connection->query(" SELECT name, @@ -144,10 +146,10 @@ WHERE type IN ('U', 'V') ") as $row) { - $tables[] = array( + $tables[] = [ 'name' => $row->name, 'view' => (bool) $row->view, - ); + ]; } return $tables; @@ -159,7 +161,7 @@ */ public function getColumns($table) { - $columns = array(); + $columns = []; foreach ($this->connection->query(" SELECT c.name AS name, @@ -203,7 +205,7 @@ */ public function getIndexes($table) { - $indexes = array(); + $indexes = []; foreach ($this->connection->query(" SELECT i.name AS name, @@ -240,7 +242,7 @@ public function getForeignKeys($table) { // Does't work with multicolumn foreign keys - $keys = array(); + $keys = []; foreach ($this->connection->query(" SELECT fk.name AS name, @@ -269,7 +271,7 @@ */ public function getColumnTypes(\PDOStatement $statement) { - $types = array(); + $types = []; $count = $statement->columnCount(); for ($col = 0; $col < $count; $col++) { $meta = $statement->getColumnMeta($col); diff -Nru php-nette-2.3.10/Nette/Database/Helpers.php php-nette-2.4-20160731/Nette/Database/Helpers.php --- php-nette-2.3.10/Nette/Database/Helpers.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Helpers.php 2016-07-31 17:46:34.000000000 +0000 @@ -16,21 +16,22 @@ */ class Helpers { + use Nette\StaticClass; + /** @var int maximum SQL length */ public static $maxLength = 100; /** @var array */ - public static $typePatterns = array( + public static $typePatterns = [ '^_' => IStructure::FIELD_TEXT, // PostgreSQL arrays - 'BYTEA|BLOB|BIN' => IStructure::FIELD_BINARY, - 'TEXT|CHAR|POINT|INTERVAL' => IStructure::FIELD_TEXT, - 'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => IStructure::FIELD_INTEGER, - 'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => IStructure::FIELD_FLOAT, - '^TIME$' => IStructure::FIELD_TIME, - 'TIME' => IStructure::FIELD_DATETIME, // DATETIME, TIMESTAMP + '(TINY|SMALL|SHORT|MEDIUM|BIG|LONG)(INT)?|INT(EGER|\d+| IDENTITY)?|(SMALL|BIG|)SERIAL\d*|COUNTER|YEAR|BYTE|LONGLONG|UNSIGNED BIG INT' => IStructure::FIELD_INTEGER, + '(NEW)?DEC(IMAL)?(\(.*)?|NUMERIC|REAL|DOUBLE( PRECISION)?|FLOAT\d*|(SMALL)?MONEY|CURRENCY|NUMBER' => IStructure::FIELD_FLOAT, + 'BOOL(EAN)?' => IStructure::FIELD_BOOL, + 'TIME' => IStructure::FIELD_TIME, 'DATE' => IStructure::FIELD_DATE, - 'BOOL' => IStructure::FIELD_BOOL, - ); + '(SMALL)?DATETIME(OFFSET)?\d*|TIME(STAMP)?' => IStructure::FIELD_DATETIME, + 'BYTEA|(TINY|MEDIUM|LONG|)BLOB|(LONG )?(VAR)?BINARY|IMAGE' => IStructure::FIELD_BINARY, + ]; /** @@ -118,7 +119,7 @@ } elseif (is_string($param)) { $length = Nette\Utils\Strings::length($param); - $truncated = Nette\Utils\Strings::truncate($param, Helpers::$maxLength); + $truncated = Nette\Utils\Strings::truncate($param, self::$maxLength); $text = htmlspecialchars($connection ? $connection->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8'); return '' . $text . ''; @@ -145,7 +146,7 @@ */ public static function detectTypes(\PDOStatement $statement) { - $types = array(); + $types = []; $count = $statement->columnCount(); // driver must be meta-aware, see PHP bugs #53782, #54695 for ($col = 0; $col < $count; $col++) { $meta = $statement->getColumnMeta($col); @@ -169,7 +170,7 @@ if (!isset($cache[$type])) { $cache[$type] = 'string'; foreach (self::$typePatterns as $s => $val) { - if (preg_match("#$s#i", $type)) { + if (preg_match("#^($s)$#i", $type)) { return $cache[$type] = $val; } } @@ -236,7 +237,7 @@ public static function toPairs(array $rows, $key = NULL, $value = NULL) { if (!$rows) { - return array(); + return []; } $keys = array_keys((array) reset($rows)); @@ -251,7 +252,7 @@ } } - $return = array(); + $return = []; if ($key === NULL) { foreach ($rows as $row) { $return[] = ($value === NULL ? $row : $row[$value]); @@ -265,4 +266,27 @@ return $return; } + + /** + * Finds duplicate columns in select statement + * @param \PDOStatement + * @return string + */ + public static function findDuplicates(\PDOStatement $statement) + { + $cols = []; + for ($i = 0; $i < $statement->columnCount(); $i++) { + $meta = $statement->getColumnMeta($i); + $cols[$meta['name']][] = isset($meta['table']) ? $meta['table'] : ''; + } + $duplicates = []; + foreach ($cols as $name => $tables) { + if (count($tables) > 1) { + $tables = array_filter(array_unique($tables)); + $duplicates[] = "'$name'" . ($tables ? ' (from ' . implode(', ', $tables) . ')' : ''); + } + } + return implode(', ', $duplicates); + } + } diff -Nru php-nette-2.3.10/Nette/Database/ResultSet.php php-nette-2.4-20160731/Nette/Database/ResultSet.php --- php-nette-2.3.10/Nette/Database/ResultSet.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/ResultSet.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,11 +13,11 @@ /** * Represents a result set. - * - * @property-read Connection $connection */ -class ResultSet extends Nette\Object implements \Iterator, IRowContainer +class ResultSet implements \Iterator, IRowContainer { + use Nette\SmartObject; + /** @var Connection */ private $connection; @@ -61,8 +61,8 @@ if (substr($queryString, 0, 2) === '::') { $connection->getPdo()->{substr($queryString, 2)}(); } elseif ($queryString !== NULL) { - static $types = array('boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT, - 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL); + static $types = ['boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT, + 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL]; $this->pdoStatement = $connection->getPdo()->prepare($queryString); foreach ($params as $key => $value) { $type = gettype($value); @@ -253,6 +253,10 @@ if (!$data) { $this->pdoStatement->closeCursor(); return FALSE; + + } elseif ($this->result === NULL && count($data) !== $this->pdoStatement->columnCount()) { + $duplicates = Helpers::findDuplicates($this->pdoStatement); + trigger_error("Found duplicate columns in database result set: $duplicates.", E_USER_NOTICE); } $row = new Row; @@ -262,10 +266,6 @@ } } - if ($this->result === NULL && count($data) !== $this->pdoStatement->columnCount()) { - trigger_error('Found duplicate columns in database result set.', E_USER_NOTICE); - } - $this->resultKey++; return $this->result = $row; } diff -Nru php-nette-2.3.10/Nette/Database/SqlLiteral.php php-nette-2.4-20160731/Nette/Database/SqlLiteral.php --- php-nette-2.3.10/Nette/Database/SqlLiteral.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/SqlLiteral.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * SQL literal value. */ -class SqlLiteral extends Nette\Object +class SqlLiteral { + use Nette\SmartObject; + /** @var string */ private $value; @@ -22,7 +24,7 @@ private $parameters; - public function __construct($value, array $parameters = array()) + public function __construct($value, array $parameters = []) { $this->value = (string) $value; $this->parameters = $parameters; diff -Nru php-nette-2.3.10/Nette/Database/SqlPreprocessor.php php-nette-2.4-20160731/Nette/Database/SqlPreprocessor.php --- php-nette-2.3.10/Nette/Database/SqlPreprocessor.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/SqlPreprocessor.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * SQL preprocessor. */ -class SqlPreprocessor extends Nette\Object +class SqlPreprocessor { + use Nette\SmartObject; + /** @var Connection */ private $connection; @@ -50,9 +52,9 @@ $this->params = $params; $this->counter = 0; $prev = -1; - $this->remaining = array(); + $this->remaining = []; $this->arrayMode = NULL; - $res = array(); + $res = []; while ($this->counter < count($params)) { $param = $params[$this->counter++]; @@ -66,15 +68,15 @@ $this->arrayMode = NULL; $res[] = Nette\Utils\Strings::replace( $param, - '~\'[^\']*+\'|"[^"]*+"|\?[a-z]*|^\s*+(?:INSERT|REPLACE)\b|\b(?:SET|WHERE|HAVING|ORDER BY|GROUP BY|KEY UPDATE)(?=[\s?]*+\z)|/\*.*?\*/|--[^\n]*~si', - array($this, 'callback') + '~\'[^\']*+\'|"[^"]*+"|\?[a-z]*|^\s*+(?:INSERT|REPLACE)\b|\b(?:SET|WHERE|HAVING|ORDER BY|GROUP BY|KEY UPDATE)(?=\s*\z|\s*\?)|/\*.*?\*/|--[^\n]*~si', + [$this, 'callback'] ); } else { throw new Nette\InvalidArgumentException('There are more parameters than placeholders.'); } } - return array(implode(' ', $res), $this->remaining); + return [implode(' ', $res), $this->remaining]; } @@ -92,7 +94,7 @@ return $m; } else { // command - static $modes = array( + static $modes = [ 'INSERT' => 'values', 'REPLACE' => 'values', 'KEY UPDATE' => 'set', @@ -101,7 +103,7 @@ 'HAVING' => 'and', 'ORDER BY' => 'order', 'GROUP BY' => 'order', - ); + ]; $this->arrayMode = $modes[ltrim(strtoupper($m))]; return $m; } @@ -137,11 +139,11 @@ } elseif ($value instanceof SqlLiteral) { $prep = clone $this; - list($res, $params) = $prep->process(array_merge(array($value->__toString()), $value->getParameters())); + list($res, $params) = $prep->process(array_merge([$value->__toString()], $value->getParameters())); $this->remaining = array_merge($this->remaining, $params); return $res; - } elseif ($value instanceof \DateTime || $value instanceof \DateTimeInterface) { + } elseif ($value instanceof \DateTimeInterface) { return $this->driver->formatDateTime($value); } elseif ($value instanceof \DateInterval) { @@ -168,7 +170,7 @@ } if (is_array($value)) { - $vx = $kx = array(); + $vx = $kx = []; if ($mode === 'auto') { $mode = $this->arrayMode; } @@ -179,7 +181,7 @@ $kx[] = $this->delimite($k); } foreach ($value as $val) { - $vx2 = array(); + $vx2 = []; foreach ($val as $v) { $vx2[] = $this->formatValue($v); } @@ -241,7 +243,7 @@ throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode."); } - } elseif (in_array($mode, array('and', 'or', 'set', 'values', 'order'), TRUE)) { + } elseif (in_array($mode, ['and', 'or', 'set', 'values', 'order'], TRUE)) { $type = gettype($value); throw new Nette\InvalidArgumentException("Placeholder ?$mode expects array or Traversable object, $type given."); @@ -256,7 +258,7 @@ private function delimite($name) { - return implode('.', array_map(array($this->driver, 'delimite'), explode('.', $name))); + return implode('.', array_map([$this->driver, 'delimite'], explode('.', $name))); } } diff -Nru php-nette-2.3.10/Nette/Database/Structure.php php-nette-2.4-20160731/Nette/Database/Structure.php --- php-nette-2.3.10/Nette/Database/Structure.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Structure.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Cached reflection of database structure. */ -class Structure extends Nette\Object implements IStructure +class Structure implements IStructure { + use Nette\SmartObject; + /** @var Connection */ protected $connection; @@ -105,7 +107,7 @@ } else { if (!isset($this->structure['hasMany'][$table])) { - return array(); + return []; } return $this->structure['hasMany'][$table]; } @@ -126,7 +128,7 @@ } else { if (!isset($this->structure['belongsTo'][$table])) { - return array(); + return []; } return $this->structure['belongsTo'][$table]; } @@ -152,7 +154,7 @@ return; } - $this->structure = $this->cache->load('structure', array($this, 'loadStructure')); + $this->structure = $this->cache->load('structure', [$this, 'loadStructure']); } @@ -163,7 +165,7 @@ { $driver = $this->connection->getSupplementalDriver(); - $structure = array(); + $structure = []; $structure['tables'] = $driver->getTables(); foreach ($structure['tables'] as $tablePair) { @@ -198,7 +200,7 @@ protected function analyzePrimaryKey(array $columns) { - $primary = array(); + $primary = []; foreach ($columns as $column) { if ($column['primary']) { $primary[] = $column['name']; @@ -217,13 +219,14 @@ protected function analyzeForeignKeys(& $structure, $table) { + $lowerTable = strtolower($table); foreach ($this->connection->getSupplementalDriver()->getForeignKeys($table) as $row) { - $structure['belongsTo'][strtolower($table)][$row['local']] = $row['table']; + $structure['belongsTo'][$lowerTable][$row['local']] = $row['table']; $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local']; } - if (isset($structure['belongsTo'][$table])) { - uksort($structure['belongsTo'][$table], function ($a, $b) { + if (isset($structure['belongsTo'][$lowerTable])) { + uksort($structure['belongsTo'][$lowerTable], function ($a, $b) { return strlen($a) - strlen($b); }); } diff -Nru php-nette-2.3.10/Nette/Database/Table/ActiveRow.php php-nette-2.4-20160731/Nette/Database/Table/ActiveRow.php --- php-nette-2.3.10/Nette/Database/Table/ActiveRow.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Table/ActiveRow.php 2016-07-31 17:46:34.000000000 +0000 @@ -98,7 +98,7 @@ } } else { - $primaryVal = array(); + $primaryVal = []; foreach ($primary as $key) { if (!isset($this->data[$key])) { if ($need) { @@ -135,7 +135,7 @@ { $row = $this->table->getReferencedTable($this, $key, $throughColumn); if ($row === FALSE) { - throw new Nette\MemberAccessException("No reference found for \${$this->table->name}->ref($key)."); + throw new Nette\MemberAccessException("No reference found for \${$this->table->getName()}->ref($key)."); } return $row; @@ -152,7 +152,7 @@ { $groupedSelection = $this->table->getReferencingTable($key, $throughColumn, $this[$this->table->getPrimary()]); if (!$groupedSelection) { - throw new Nette\MemberAccessException("No reference found for \${$this->table->name}->related($key)."); + throw new Nette\MemberAccessException("No reference found for \${$this->table->getName()}->related($key)."); } return $groupedSelection; @@ -172,7 +172,7 @@ $primary = $this->getPrimary(); if (!is_array($primary)) { - $primary = array($this->table->getPrimary() => $primary); + $primary = [$this->table->getPrimary() => $primary]; } $selection = $this->table->createSelectionInstance() @@ -323,7 +323,7 @@ { if ($this->table->accessColumn($key, $selectColumn) && !$this->dataRefreshed) { if (!isset($this->table[$this->getSignature()])) { - throw new Nette\InvalidStateException('Database refetch failed; row does not exist!'); + throw new Nette\InvalidStateException("Database refetch failed; row with signature '{$this->getSignature()}' does not exist!"); } $this->data = $this->table[$this->getSignature()]->data; $this->dataRefreshed = TRUE; diff -Nru php-nette-2.3.10/Nette/Database/Table/GroupedSelection.php php-nette-2.4-20160731/Nette/Database/Table/GroupedSelection.php --- php-nette-2.3.10/Nette/Database/Table/GroupedSelection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Table/GroupedSelection.php 2016-07-31 17:46:34.000000000 +0000 @@ -52,7 +52,7 @@ * Sets active group. * @internal * @param int primary key of grouped rows - * @return GroupedSelection + * @return self */ public function setActive($active) { @@ -61,24 +61,24 @@ } - public function select($columns) + public function select($columns, ...$params) { if (!$this->sqlBuilder->getSelect()) { $this->sqlBuilder->addSelect("$this->name.$this->column"); } - return call_user_func_array('parent::select', func_get_args()); + return parent::select($columns, ...$params); } - public function order($columns) + public function order($columns, ...$params) { if (!$this->sqlBuilder->getOrder()) { // improve index utilization $this->sqlBuilder->addOrder("$this->name.$this->column" . (preg_match('~\bDESC\z~i', $columns) ? ' DESC' : '')); } - return call_user_func_array('parent::order', func_get_args()); + return parent::order($columns, ...$params); } @@ -87,10 +87,10 @@ public function aggregation($function) { - $aggregation = & $this->getRefTable($refPath)->aggregation[$refPath . $function . $this->getSql() . json_encode($this->sqlBuilder->getParameters())]; + $aggregation = & $this->getRefTable($refPath)->aggregation[$refPath . $function . $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns())]; if ($aggregation === NULL) { - $aggregation = array(); + $aggregation = []; $selection = $this->createSelectionInstance(); $selection->getSqlBuilder()->importConditions($this->getSqlBuilder()); @@ -142,8 +142,8 @@ } parent::execute(); $this->sqlBuilder->setLimit($limit, NULL); - $data = array(); - $offset = array(); + $data = []; + $offset = []; $this->accessColumn($this->column); foreach ((array) $this->rows as $key => $row) { $ref = & $data[$row[$this->column]]; @@ -163,7 +163,7 @@ $this->observeCache = $this; if ($this->data === NULL) { - $this->data = array(); + $this->data = []; } else { foreach ($this->data as $row) { $row->setTable($this); // injects correct parent GroupedSelection diff -Nru php-nette-2.3.10/Nette/Database/Table/Selection.php php-nette-2.4-20160731/Nette/Database/Table/Selection.php --- php-nette-2.3.10/Nette/Database/Table/Selection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Table/Selection.php 2016-07-31 17:46:34.000000000 +0000 @@ -15,11 +15,11 @@ /** * Filtered table representation. * Selection is based on the great library NotORM http://www.notorm.com written by Jakub Vrana. - * - * @property-read string $sql */ -class Selection extends Nette\Object implements \Iterator, IRowContainer, \ArrayAccess, \Countable +class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable { + use Nette\SmartObject; + /** @var Context */ protected $context; @@ -59,14 +59,11 @@ /** @var string */ protected $generalCacheKey; - /** @var array */ - protected $generalCacheTraceKey; - /** @var string */ protected $specificCacheKey; /** @var array of [conditions => [key => IRow]]; used by GroupedSelection */ - protected $aggregation = array(); + protected $aggregation = []; /** @var array of touched columns */ protected $accessedColumns; @@ -78,7 +75,7 @@ protected $observeCache = FALSE; /** @var array of primary key values */ - protected $keys = array(); + protected $keys = []; /** @@ -113,22 +110,6 @@ } - /** @deprecated */ - public function getConnection() - { - trigger_error(__METHOD__ . '() is deprecated; use DI container to autowire Nette\Database\Connection instead.', E_USER_DEPRECATED); - return $this->context->getConnection(); - } - - - /** @deprecated */ - public function getDatabaseReflection() - { - trigger_error(__METHOD__ . '() is deprecated; use DI container to autowire Nette\Database\IConventions instead.', E_USER_DEPRECATED); - return $this->conventions; - } - - /** * @return string */ @@ -194,7 +175,7 @@ if ($this->cache && $this->previousAccessedColumns === NULL) { $this->accessedColumns = $this->previousAccessedColumns = $this->cache->load($this->getGeneralCacheKey()); if ($this->previousAccessedColumns === NULL) { - $this->previousAccessedColumns = array(); + $this->previousAccessedColumns = []; } } @@ -252,7 +233,7 @@ $row = $this->fetch(); if ($row) { - return $column ? $row[$column] : current($row->toArray()); + return $column ? $row[$column] : array_values($row->toArray())[0]; } return FALSE; @@ -295,10 +276,10 @@ * @param string for example "column, MD5(column) AS column_md5" * @return self */ - public function select($columns) + public function select($columns, ...$params) { $this->emptyResultSet(); - call_user_func_array(array($this->sqlBuilder, 'addSelect'), func_get_args()); + $this->sqlBuilder->addSelect($columns, ...$params); return $this; } @@ -332,25 +313,84 @@ * Adds where condition, more calls appends with AND. * @param string condition possibly containing ? * @param mixed - * @param mixed ... * @return self */ - public function where($condition, $parameters = array()) + public function where($condition, ...$params) + { + $this->condition($condition, $params); + return $this; + } + + + /** + * Adds ON condition when joining specified table, more calls appends with AND. + * @param string table chain or table alias for which you need additional left join condition + * @param string condition possibly containing ? + * @param mixed + * @return self + */ + public function joinWhere($tableChain, $condition, ...$params) + { + $this->condition($condition, $params, $tableChain); + return $this; + } + + + /** + * Adds condition, more calls appends with AND. + * @param string condition possibly containing ? + * @return void + */ + protected function condition($condition, array $params, $tableChain = NULL) { - if (is_array($condition) && $parameters === array()) { // where(array('column1' => 1, 'column2 > ?' => 2)) + $this->emptyResultSet(); + if (is_array($condition) && $params === []) { // where(array('column1' => 1, 'column2 > ?' => 2)) foreach ($condition as $key => $val) { if (is_int($key)) { - $this->where($val); // where('full condition') + $this->condition($val, [], $tableChain); // where('full condition') } else { - $this->where($key, $val); // where('column', 1) + $this->condition($key, [$val], $tableChain); // where('column', 1) } } - return $this; + } elseif ($tableChain) { + $this->sqlBuilder->addJoinCondition($tableChain, $condition, ...$params); + } else { + $this->sqlBuilder->addWhere($condition, ...$params); } + } - $this->emptyResultSet(); - call_user_func_array(array($this->sqlBuilder, 'addWhere'), func_get_args()); - return $this; + + /** + * Adds where condition using the OR operator between parameters. + * More calls appends with AND. + * @param array ['column1' => 1, 'column2 > ?' => 2, 'full condition'] + * @return self + * @throws \Nette\InvalidArgumentException + */ + public function whereOr(array $parameters) + { + if (count($parameters) < 2) { + return $this->where($parameters); + } + $columns = []; + $values = []; + foreach ($parameters as $key => $val) { + if (is_int($key)) { // whereOr(['full condition']) + $columns[] = $val; + } elseif (strpos($key, '?') === FALSE) { // whereOr(['column1' => 1]) + $columns[] = $key . ' ?'; + $values[] = $val; + } else { // whereOr(['column1 > ?' => 1]) + $qNumber = substr_count($key, '?'); + if ($qNumber > 1 && (!is_array($val) || $qNumber !== count($val))) { + throw new Nette\InvalidArgumentException('Argument count does not match placeholder count.'); + } + $columns[] = $key; + $values = array_merge($values, $qNumber > 1 ? $val : [$val]); + } + } + $columnsString = '(' . implode(') OR (', $columns) . ')'; + return $this->where($columnsString, $values); } @@ -359,10 +399,10 @@ * @param string for example 'column1, column2 DESC' * @return self */ - public function order($columns) + public function order($columns, ...$params) { $this->emptyResultSet(); - call_user_func_array(array($this->sqlBuilder, 'addOrder'), func_get_args()); + $this->sqlBuilder->addOrder($columns, ...$params); return $this; } @@ -392,6 +432,9 @@ if (func_num_args() > 2) { $numOfPages = (int) ceil($this->count('*') / $itemsPerPage); } + if ($page < 1) { + $itemsPerPage = 0; + } return $this->limit($itemsPerPage, ($page - 1) * $itemsPerPage); } @@ -401,10 +444,10 @@ * @param string * @return self */ - public function group($columns) + public function group($columns, ...$params) { $this->emptyResultSet(); - call_user_func_array(array($this->sqlBuilder, 'setGroup'), func_get_args()); + $this->sqlBuilder->setGroup($columns, ...$params); return $this; } @@ -414,10 +457,23 @@ * @param string * @return self */ - public function having($having) + public function having($having, ...$params) { $this->emptyResultSet(); - call_user_func_array(array($this->sqlBuilder, 'setHaving'), func_get_args()); + $this->sqlBuilder->setHaving($having, ...$params); + return $this; + } + + + /** + * Aliases table. Example ':book:book_tag.tag', 'tg' + * @param string + * @param string + * @return self + */ + public function alias($tableChain, $alias) + { + $this->sqlBuilder->addAlias($tableChain, $alias); return $this; } @@ -510,14 +566,14 @@ } catch (Nette\Database\DriverException $exception) { if (!$this->sqlBuilder->getSelect() && $this->previousAccessedColumns) { $this->previousAccessedColumns = FALSE; - $this->accessedColumns = array(); + $this->accessedColumns = []; $result = $this->query($this->getSql()); } else { throw $exception; } } - $this->rows = array(); + $this->rows = []; $usedPrimary = TRUE; foreach ($result->getPdoStatement() as $key => $row) { $row = $this->createRow($result->normalizeRow($row)); @@ -559,23 +615,23 @@ } - protected function emptyResultSet($saveCache = TRUE, $deleteRererencedCache = TRUE) + protected function emptyResultSet($clearCache = TRUE, $deleteRererencedCache = TRUE) { - if ($this->rows !== NULL && $saveCache) { + if ($this->rows !== NULL && $clearCache) { $this->saveCacheState(); } - if ($saveCache) { - // null only if missing some column - $this->generalCacheTraceKey = NULL; + if ($clearCache) { + // not null in case of missing some column + $this->previousAccessedColumns = NULL; + $this->generalCacheKey = NULL; } $this->rows = NULL; $this->specificCacheKey = NULL; - $this->generalCacheKey = NULL; - $this->refCache['referencingPrototype'] = array(); + $this->refCache['referencingPrototype'] = []; if ($deleteRererencedCache) { - $this->refCache['referenced'] = array(); + $this->refCache['referenced'] = []; } } @@ -600,7 +656,7 @@ /** * Returns Selection parent for caching. - * @return Selection + * @return self */ protected function getRefTable(& $refPath) { @@ -627,16 +683,13 @@ return $this->generalCacheKey; } - $key = array(__CLASS__, $this->name, $this->sqlBuilder->getConditions()); - if (!$this->generalCacheTraceKey) { - $trace = array(); - foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) { - $trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : NULL; - }; - $this->generalCacheTraceKey = $trace; - } + $key = [__CLASS__, $this->name, $this->sqlBuilder->getConditions()]; + $trace = []; + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) { + $trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : NULL; + }; - $key[] = $this->generalCacheTraceKey; + $key[] = $trace; return $this->generalCacheKey = md5(serialize($key)); } @@ -652,7 +705,7 @@ return $this->specificCacheKey; } - return $this->specificCacheKey = md5($this->getSql() . json_encode($this->sqlBuilder->getParameters())); + return $this->specificCacheKey = $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns()); } @@ -676,13 +729,11 @@ } if ($selectColumn && $this->previousAccessedColumns && ($key === NULL || !isset($this->previousAccessedColumns[$key])) && !$this->sqlBuilder->getSelect()) { - $this->previousAccessedColumns = array(); - if ($this->sqlBuilder->getLimit()) { $generalCacheKey = $this->generalCacheKey; $sqlBuilder = $this->sqlBuilder; - $primaryValues = array(); + $primaryValues = []; foreach ((array) $this->rows as $row) { $primary = $row->getPrimary(); $primaryValues[] = is_array($primary) ? array_values($primary) : $primary; @@ -694,10 +745,12 @@ $this->wherePrimary($primaryValues); $this->generalCacheKey = $generalCacheKey; + $this->previousAccessedColumns = []; $this->execute(); $this->sqlBuilder = $sqlBuilder; } else { $this->emptyResultSet(FALSE); + $this->previousAccessedColumns = []; $this->execute(); } @@ -765,7 +818,7 @@ $primaryKey = $this->context->getInsertId( ($tmp = $this->getPrimarySequence()) - ? implode('.', array_map(array($this->context->getConnection()->getSupplementalDriver(), 'delimite'), explode('.', $tmp))) + ? implode('.', array_map([$this->context->getConnection()->getSupplementalDriver(), 'delimite'], explode('.', $tmp))) : NULL ); if ($primaryKey === FALSE) { @@ -774,7 +827,7 @@ } if (is_array($this->getPrimary())) { - $primaryKey = array(); + $primaryKey = []; foreach ((array) $this->getPrimary() as $key) { if (!isset($data[$key])) { @@ -828,7 +881,7 @@ return $this->context->queryArgs( $this->sqlBuilder->buildUpdateQuery(), - array_merge(array($data), $this->sqlBuilder->getParameters()) + array_merge([$data], $this->sqlBuilder->getParameters()) )->getRowCount(); } @@ -873,7 +926,7 @@ $cacheKeys = & $referenced['cacheKeys']; if ($selection === NULL || ($checkPrimaryKey !== NULL && !isset($cacheKeys[$checkPrimaryKey]))) { $this->execute(); - $cacheKeys = array(); + $cacheKeys = []; foreach ($this->rows as $row) { if ($row[$column] === NULL) { continue; @@ -887,7 +940,7 @@ $selection = $this->createSelectionInstance($table); $selection->where($selection->getPrimary(), array_keys($cacheKeys)); } else { - $selection = array(); + $selection = []; } } @@ -959,7 +1012,9 @@ public function next() { - next($this->keys); + do { + next($this->keys); + } while (($key = current($this->keys)) !== FALSE && !isset($this->data[$key])); } diff -Nru php-nette-2.3.10/Nette/Database/Table/SqlBuilder.php php-nette-2.4-20160731/Nette/Database/Table/SqlBuilder.php --- php-nette-2.3.10/Nette/Database/Table/SqlBuilder.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Database/Table/SqlBuilder.php 2016-07-31 17:46:34.000000000 +0000 @@ -19,8 +19,9 @@ * Builds SQL query. * SqlBuilder is based on great library NotORM http://www.notorm.com written by Jakub Vrana. */ -class SqlBuilder extends Nette\Object +class SqlBuilder { + use Nette\SmartObject; /** @var string */ protected $tableName; @@ -32,25 +33,29 @@ protected $delimitedTable; /** @var array of column to select */ - protected $select = array(); + protected $select = []; /** @var array of where conditions */ - protected $where = array(); + protected $where = []; + + /** @var array of array of join conditions */ + protected $joinCondition = []; /** @var array of where conditions for caching */ - protected $conditions = array(); + protected $conditions = []; /** @var array of parameters passed to where conditions */ - protected $parameters = array( - 'select' => array(), - 'where' => array(), - 'group' => array(), - 'having' => array(), - 'order' => array(), - ); + protected $parameters = [ + 'select' => [], + 'joinCondition' => [], + 'where' => [], + 'group' => [], + 'having' => [], + 'order' => [], + ]; /** @var array or columns to order by */ - protected $order = array(); + protected $order = []; /** @var int number of rows to fetch */ protected $limit = NULL; @@ -64,6 +69,15 @@ /** @var string grouping condition */ protected $having = ''; + /** @var array of reserved table names associated with chain */ + protected $reservedTableNames = []; + + /** @var array of table aliases */ + protected $aliases = []; + + /** @var string currently parsing alias for joins */ + protected $currentAlias = NULL; + /** @var ISupplementalDriver */ private $driver; @@ -73,6 +87,9 @@ /** @var array */ private $cacheTableList; + /** @var array of expanding joins */ + private $expandingJoins = []; + public function __construct($tableName, Context $context) { @@ -80,7 +97,9 @@ $this->driver = $context->getConnection()->getSupplementalDriver(); $this->conventions = $context->getConventions(); $this->structure = $context->getStructure(); - $this->delimitedTable = implode('.', array_map(array($this->driver, 'delimite'), explode('.', $tableName))); + $tableNameParts = explode('.', $tableName); + $this->delimitedTable = implode('.', array_map([$this->driver, 'delimite'], $tableNameParts)); + $this->checkUniqueTableName(end($tableNameParts), $tableName); } @@ -118,16 +137,58 @@ /** + * Returns select query hash for caching. + * @return string + */ + public function getSelectQueryHash($columns = NULL) + { + $parts = [ + 'delimitedTable' => $this->delimitedTable, + 'queryCondition' => $this->buildConditions(), + 'queryEnd' => $this->buildQueryEnd(), + $this->aliases, + $this->limit, $this->offset, + ]; + if ($this->select) { + $parts[] = $this->select; + } elseif ($columns) { + $parts[] = [$this->delimitedTable, $columns]; + } elseif ($this->group && !$this->driver->isSupported(ISupplementalDriver::SUPPORT_SELECT_UNGROUPED_COLUMNS)) { + $parts[] = [$this->group]; + } else { + $parts[] = "{$this->delimitedTable}.*"; + } + return $this->getConditionHash(json_encode($parts), [ + $this->parameters['select'], + $this->parameters['joinCondition'], + $this->parameters['where'], + $this->parameters['group'], + $this->parameters['having'], + $this->parameters['order'] + ]); + } + + + /** * Returns SQL query. * @param string list of columns * @return string */ public function buildSelectQuery($columns = NULL) { + if (!$this->order && ($this->limit !== NULL || $this->offset)) { + $this->order = array_map( + function ($col) { return "$this->tableName.$col"; }, + (array) $this->conventions->getPrimary($this->tableName) + ); + } + + $queryJoinConditions = $this->buildJoinConditions(); $queryCondition = $this->buildConditions(); $queryEnd = $this->buildQueryEnd(); - $joins = array(); + $joins = []; + $finalJoinConditions = $this->parseJoinConditions($joins, $queryJoinConditions); $this->parseJoins($joins, $queryCondition); $this->parseJoins($joins, $queryEnd); @@ -137,27 +198,25 @@ } elseif ($columns) { $prefix = $joins ? "{$this->delimitedTable}." : ''; - $cols = array(); + $cols = []; foreach ($columns as $col) { $cols[] = $prefix . $col; } $querySelect = $this->buildSelect($cols); } elseif ($this->group && !$this->driver->isSupported(ISupplementalDriver::SUPPORT_SELECT_UNGROUPED_COLUMNS)) { - $querySelect = $this->buildSelect(array($this->group)); + $querySelect = $this->buildSelect([$this->group]); $this->parseJoins($joins, $querySelect); } else { $prefix = $joins ? "{$this->delimitedTable}." : ''; - $querySelect = $this->buildSelect(array($prefix . '*')); + $querySelect = $this->buildSelect([$prefix . '*']); } - $queryJoins = $this->buildQueryJoins($joins); + $queryJoins = $this->buildQueryJoins($joins, $finalJoinConditions); $query = "{$querySelect} FROM {$this->delimitedTable}{$queryJoins}{$queryCondition}{$queryEnd}"; - if ($this->limit !== NULL || $this->offset > 0) { - $this->driver->applyLimit($query, $this->limit, max(0, $this->offset)); - } + $this->driver->applyLimit($query, $this->limit, $this->offset); return $this->tryDelimite($query); } @@ -165,8 +224,12 @@ public function getParameters() { + if (!isset($this->parameters['joinConditionSorted'])) { + $this->buildSelectQuery(); + } return array_merge( $this->parameters['select'], + $this->parameters['joinConditionSorted'] ? call_user_func_array('array_merge', $this->parameters['joinConditionSorted']) : [], $this->parameters['where'], $this->parameters['group'], $this->parameters['having'], @@ -178,21 +241,25 @@ public function importConditions(SqlBuilder $builder) { $this->where = $builder->where; + $this->joinCondition = $builder->joinCondition; $this->parameters['where'] = $builder->parameters['where']; + $this->parameters['joinCondition'] = $builder->parameters['joinCondition']; $this->conditions = $builder->conditions; + $this->aliases = $builder->aliases; + $this->reservedTableNames = $builder->reservedTableNames; } /********************* SQL selectors ****************d*g**/ - public function addSelect($columns) + public function addSelect($columns, ...$params) { if (is_array($columns)) { throw new Nette\InvalidArgumentException('Select column must be a string.'); } $this->select[] = $columns; - $this->parameters['select'] = array_merge($this->parameters['select'], array_slice(func_get_args(), 1)); + $this->parameters['select'] = array_merge($this->parameters['select'], $params); } @@ -202,36 +269,49 @@ } - public function addWhere($condition, $parameters = array()) + public function addWhere($condition, ...$params) + { + return $this->addCondition($condition, $params, $this->where, $this->parameters['where']); + } + + + public function addJoinCondition($tableChain, $condition, ...$params) { - if (is_array($condition) && is_array($parameters) && !empty($parameters)) { - return $this->addWhereComposition($condition, $parameters); + $this->parameters['joinConditionSorted'] = NULL; + if (!isset($this->joinCondition[$tableChain])) { + $this->joinCondition[$tableChain] = $this->parameters['joinCondition'][$tableChain] = []; + } + return $this->addCondition($condition, $params, $this->joinCondition[$tableChain], $this->parameters['joinCondition'][$tableChain]); + } + + + protected function addCondition($condition, array $params, array & $conditions, array & $conditionsParameters) + { + if (is_array($condition) && !empty($params[0]) && is_array($params[0])) { + return $this->addConditionComposition($condition, $params[0], $conditions, $conditionsParameters); } - $args = func_get_args(); - $hash = md5(json_encode($args)); + $hash = $this->getConditionHash($condition, $params); if (isset($this->conditions[$hash])) { return FALSE; } $this->conditions[$hash] = $condition; $placeholderCount = substr_count($condition, '?'); - if ($placeholderCount > 1 && count($args) === 2 && is_array($parameters)) { - $args = $parameters; - } else { - array_shift($args); + if ($placeholderCount > 1 && count($params) === 1 && is_array($params[0])) { + $params = $params[0]; } $condition = trim($condition); - if ($placeholderCount === 0 && count($args) === 1) { + if ($placeholderCount === 0 && count($params) === 1) { $condition .= ' ?'; - } elseif ($placeholderCount !== count($args)) { + } elseif ($placeholderCount !== count($params)) { throw new Nette\InvalidArgumentException('Argument count does not match placeholder count.'); } $replace = NULL; $placeholderNum = 0; - foreach ($args as $arg) { + foreach ($params as $arg) { preg_match('#(?:.*?\?.*?){' . $placeholderNum . '}(((?:&|\||^|~|\+|-|\*|/|%|\(|,|<|>|=|(?<=\W|^)(?:REGEXP|ALL|AND|ANY|BETWEEN|EXISTS|IN|[IR]?LIKE|OR|NOT|SOME|INTERVAL))\s*)?(?:\(\?\)|\?))#s', $condition, $match, PREG_OFFSET_CAPTURE); $hasOperator = ($match[1][0] === '?' && $match[1][1] === 0) ? TRUE : !empty($match[2][0]); @@ -268,9 +348,9 @@ if ($this->driver->isSupported(ISupplementalDriver::SUPPORT_SUBSELECT)) { $arg = NULL; $replace = $match[2][0] . '(' . $clone->getSql() . ')'; - $this->parameters['where'] = array_merge($this->parameters['where'], $clone->getSqlBuilder()->getParameters()); + $conditionsParameters = array_merge($conditionsParameters, $clone->getSqlBuilder()->getParameters()); } else { - $arg = array(); + $arg = []; foreach ($clone as $row) { $arg[] = array_values(iterator_to_array($row)); } @@ -294,16 +374,16 @@ $arg = NULL; } else { $replace = $match[2][0] . '(?)'; - $this->parameters['where'][] = $arg; + $conditionsParameters[] = $arg; } } } elseif ($arg instanceof SqlLiteral) { - $this->parameters['where'][] = $arg; + $conditionsParameters[] = $arg; } else { if (!$hasOperator) { $replace = '= ?'; } - $this->parameters['where'][] = $arg; + $conditionsParameters[] = $arg; } if ($replace) { @@ -316,7 +396,7 @@ } } - $this->where[] = $condition; + $conditions[] = $condition; return TRUE; } @@ -327,10 +407,39 @@ } - public function addOrder($columns) + /** + * Adds alias. + * @return void + */ + public function addAlias($chain, $alias) + { + if (isset($chain[0]) && $chain[0] !== '.' && $chain[0] !== ':') { + $chain = '.' . $chain; // unified chain format + } + $this->checkUniqueTableName($alias, $chain); + $this->aliases[$alias] = $chain; + } + + + protected function checkUniqueTableName($tableName, $chain) + { + if (isset($this->aliases[$tableName]) && ('.' . $tableName === $chain)) { + $chain = $this->aliases[$tableName]; + } + if (isset($this->reservedTableNames[$tableName])) { + if ($this->reservedTableNames[$tableName] === $chain) { + return; + } + throw new \Nette\InvalidArgumentException("Table alias '$tableName' from chain '$chain' is already in use by chain '{$this->reservedTableNames[$tableName]}'. Please add/change alias for one of them."); + } + $this->reservedTableNames[$tableName] = $chain; + } + + + public function addOrder($columns, ...$params) { $this->order[] = $columns; - $this->parameters['order'] = array_merge($this->parameters['order'], array_slice(func_get_args(), 1)); + $this->parameters['order'] = array_merge($this->parameters['order'], $params); } @@ -366,10 +475,10 @@ } - public function setGroup($columns) + public function setGroup($columns, ...$params) { $this->group = $columns; - $this->parameters['group'] = array_slice(func_get_args(), 1); + $this->parameters['group'] = $params; } @@ -379,10 +488,10 @@ } - public function setHaving($having) + public function setHaving($having, ...$params) { $this->having = $having; - $this->parameters['having'] = array_slice(func_get_args(), 1); + $this->parameters['having'] = $params; } @@ -401,19 +510,91 @@ } + protected function parseJoinConditions(& $joins, $joinConditions) + { + $tableJoins = $leftJoinDependency = $finalJoinConditions = []; + foreach ($joinConditions as $tableChain => & $joinCondition) { + $fooQuery = $tableChain . '.foo'; + $requiredJoins = []; + $this->parseJoins($requiredJoins, $fooQuery); + $tableAlias = substr($fooQuery, 0, -4); + $tableJoins[$tableAlias] = $requiredJoins; + $leftJoinDependency[$tableAlias] = []; + $finalJoinConditions[$tableAlias] = preg_replace_callback($this->getColumnChainsRegxp(), function ($match) use ($tableAlias, & $tableJoins, & $leftJoinDependency) { + $requiredJoins = []; + $query = $this->parseJoinsCb($requiredJoins, $match); + $queryParts = explode('.', $query); + $tableJoins[$queryParts[0]] = $requiredJoins; + if ($queryParts[0] !== $tableAlias) { + foreach (array_keys($requiredJoins) as $requiredTable) { + $leftJoinDependency[$tableAlias][$requiredTable] = $requiredTable; + } + } + return $query; + }, $joinCondition); + } + $this->parameters['joinConditionSorted'] = []; + if (count($joinConditions)) { + while (reset($tableJoins)) { + $this->getSortedJoins(key($tableJoins), $leftJoinDependency, $tableJoins, $joins); + } + } + return $finalJoinConditions; + } + + + protected function getSortedJoins($table, & $leftJoinDependency, & $tableJoins, & $finalJoins) + { + if (isset($this->expandingJoins[$table])) { + $path = implode("' => '", array_map(function($value) { return $this->reservedTableNames[$value]; }, array_merge(array_keys($this->expandingJoins), [$table]))); + throw new Nette\InvalidArgumentException("Circular reference detected at left join conditions (tables '$path')."); + } + if (isset($tableJoins[$table])) { + $this->expandingJoins[$table] = TRUE; + if (isset($leftJoinDependency[$table])) { + foreach ($leftJoinDependency[$table] as $requiredTable) { + if ($requiredTable === $table) { + continue; + } + $this->getSortedJoins($requiredTable, $leftJoinDependency, $tableJoins, $finalJoins); + } + } + if ($tableJoins[$table]) { + foreach ($tableJoins[$table] as $requiredTable => $tmp) { + if ($requiredTable === $table) { + continue; + } + $this->getSortedJoins($requiredTable, $leftJoinDependency, $tableJoins, $finalJoins); + } + } + $finalJoins += $tableJoins[$table]; + $this->parameters['joinConditionSorted'] += isset($this->parameters['joinCondition'][$this->reservedTableNames[$table]]) + ? [$table => $this->parameters['joinCondition'][$this->reservedTableNames[$table]]] + : []; + unset($tableJoins[$table]); + unset($this->expandingJoins[$table]); + } + } + + protected function parseJoins(& $joins, & $query) { - $builder = $this; - $query = preg_replace_callback('~ + $query = preg_replace_callback($this->getColumnChainsRegxp(), function ($match) use (& $joins) { + return $this->parseJoinsCb($joins, $match); + }, $query); + } + + + private function getColumnChainsRegxp() + { + return '~ (?(DEFINE) (?PisLoggedIn()): ?>Logged inUnlogged
- getIdentity()): echo Dumper::toHtml($user->getIdentity(), array(Dumper::LIVE => TRUE)); else: ?>no identity
+ getIdentity()): echo Dumper::toHtml($user->getIdentity(), [Dumper::LIVE => TRUE]); else: ?>no identity
[\w_]*[a-z][\w_]* ) (?P '; endif ?> - - - - -[.:] ) (?P(?&del)? (?&word) (\((?&word)\))? ) ) (?P (?!\.) (?&node)*) \. (?P (?&word) | \* ) - ~xi', function ($match) use (& $joins, $builder) { - return $builder->parseJoinsCb($joins, $match); - }, $query); + ~xi'; } @@ -447,15 +628,33 @@ // do not make a join when referencing to the current table column - inner conditions // check it only when not making backjoin on itself - outer condition if ($keyMatches[0]['del'] === '.') { + if (count($keyMatches) > 1 && ($parent === $keyMatches[0]['key'] || $parentAlias === $keyMatches[0]['key'])) { + throw new Nette\InvalidArgumentException("Do not prefix table chain with origin table name '{$keyMatches[0]['key']}'. If you want to make self reference, please add alias."); + } if ($parent === $keyMatches[0]['key']) { return "{$parent}.{$match['column']}"; } elseif ($parentAlias === $keyMatches[0]['key']) { return "{$parentAlias}.{$match['column']}"; } } - - foreach ($keyMatches as $keyMatch) { - if ($keyMatch['del'] === ':') { + $tableChain = NULL; + foreach ($keyMatches as $index => $keyMatch) { + $isLast = !isset($keyMatches[$index + 1]); + if (!$index && isset($this->aliases[$keyMatch['key']])) { + if ($keyMatch['del'] === ':') { + throw new Nette\InvalidArgumentException("You are using has many syntax with alias (':{$keyMatch['key']}'). You have to move it to alias definition."); + } else { + $previousAlias = $this->currentAlias; + $this->currentAlias = $keyMatch['key']; + $requiredJoins = []; + $query = $this->aliases[$keyMatch['key']] . '.foo'; + $this->parseJoins($requiredJoins, $query); + $aliasJoin = array_pop($requiredJoins); + $joins += $requiredJoins; + list($table, , $parentAlias, $column, $primary) = $aliasJoin; + $this->currentAlias = $previousAlias; + } + } elseif ($keyMatch['del'] === ':') { if (isset($keyMatch['throughColumn'])) { $table = $keyMatch['key']; $belongsTo = $this->conventions->getBelongsToReference($table, $keyMatch['throughColumn']); @@ -482,14 +681,21 @@ $primary = $this->conventions->getPrimary($table); } - $tableAlias = $keyMatch['key'] ?: preg_replace('#^(.*\.)?(.*)$#', '$2', $table); - - // if we are joining itself (parent table), we must alias joining table - if ($parent === $table) { + if ($this->currentAlias && $isLast) { + $tableAlias = $this->currentAlias; + } elseif ($parent === $table) { $tableAlias = $parentAlias . '_ref'; + } elseif ($keyMatch['key']) { + $tableAlias = $keyMatch['key']; + } else { + $tableAlias = preg_replace('#^(.*\.)?(.*)$#', '$2', $table); } - $joins[$tableAlias . $column] = array($table, $tableAlias, $parentAlias, $column, $primary); + $tableChain .= $keyMatch[0]; + if (!$isLast || !$this->currentAlias) { + $this->checkUniqueTableName($tableAlias, $tableChain); + } + $joins[$tableAlias] = [$table, $tableAlias, $parentAlias, $column, $primary]; $parent = $table; $parentAlias = $tableAlias; } @@ -498,20 +704,29 @@ } - protected function buildQueryJoins(array $joins) + protected function buildQueryJoins(array $joins, array $leftJoinConditions = []) { $return = ''; - foreach ($joins as $join) { - list($joinTable, $joinAlias, $table, $tableColumn, $joinColumn) = $join; - + foreach ($joins as list($joinTable, $joinAlias, $table, $tableColumn, $joinColumn)) { $return .= " LEFT JOIN {$joinTable}" . ($joinTable !== $joinAlias ? " {$joinAlias}" : '') . - " ON {$table}.{$tableColumn} = {$joinAlias}.{$joinColumn}"; + " ON {$table}.{$tableColumn} = {$joinAlias}.{$joinColumn}" . + (isset($leftJoinConditions[$joinAlias]) ? " {$leftJoinConditions[$joinAlias]}" : ''); } return $return; } + protected function buildJoinConditions() + { + $conditions = []; + foreach ($this->joinCondition as $tableChain => $joinConditions) { + $conditions[$tableChain] = 'AND (' . implode(') AND (', $joinConditions) . ')'; + } + return $conditions; + } + + protected function buildConditions() { return $this->where ? ' WHERE (' . implode(') AND (', $this->where) . ')' : ''; @@ -536,22 +751,38 @@ protected function tryDelimite($s) { - $driver = $this->driver; - return preg_replace_callback('#(?<=[^\w`"\[?]|^)[a-z_][a-z0-9_]*(?=[^\w`"(\]]|\z)#i', function ($m) use ($driver) { - return strtoupper($m[0]) === $m[0] ? $m[0] : $driver->delimite($m[0]); + return preg_replace_callback('#(?<=[^\w`"\[?]|^)[a-z_][a-z0-9_]*(?=[^\w`"(\]]|\z)#i', function ($m) { + return strtoupper($m[0]) === $m[0] ? $m[0] : $this->driver->delimite($m[0]); }, $s); } - protected function addWhereComposition(array $columns, array $parameters) + protected function addConditionComposition(array $columns, array $parameters, array & $conditions, array & $conditionsParameters) { if ($this->driver->isSupported(ISupplementalDriver::SUPPORT_MULTI_COLUMN_AS_OR_COND)) { $conditionFragment = '(' . implode(' = ? AND ', $columns) . ' = ?) OR '; $condition = substr(str_repeat($conditionFragment, count($parameters)), 0, -4); - return $this->addWhere($condition, Nette\Utils\Arrays::flatten($parameters)); + return $this->addCondition($condition, [Nette\Utils\Arrays::flatten($parameters)], $conditions, $conditionsParameters); } else { - return $this->addWhere('(' . implode(', ', $columns) . ') IN', $parameters); + return $this->addCondition('(' . implode(', ', $columns) . ') IN', [$parameters], $conditions, $conditionsParameters); + } + } + + + private function getConditionHash($condition, $parameters) + { + foreach ($parameters as $key => & $parameter) { + if ($parameter instanceof Selection) { + $parameter = $this->getConditionHash($parameter->getSql(), $parameter->getSqlBuilder()->getParameters()); + } elseif ($parameter instanceof SqlLiteral) { + $parameter = $this->getConditionHash($parameter->__toString(), $parameter->getParameters()); + } elseif (is_object($parameter) && method_exists($parameter, '__toString')) { + $parameter = $parameter->__toString(); + } elseif (is_array($parameter) || $parameter instanceof \ArrayAccess) { + $parameter = $this->getConditionHash($key, $parameter); + } } + return md5($condition . json_encode($parameters)); } diff -Nru php-nette-2.3.10/Nette/deprecated/Callback.php php-nette-2.4-20160731/Nette/deprecated/Callback.php --- php-nette-2.3.10/Nette/deprecated/Callback.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Callback.php 2016-07-31 17:46:44.000000000 +0000 @@ -37,6 +37,7 @@ */ public function __construct($cb, $m = NULL) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if ($m !== NULL) { $cb = array($cb, $m); diff -Nru php-nette-2.3.10/Nette/deprecated/Diagnostics/Debugger.php php-nette-2.4-20160731/Nette/deprecated/Diagnostics/Debugger.php --- php-nette-2.3.10/Nette/deprecated/Diagnostics/Debugger.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Diagnostics/Debugger.php 2016-07-31 17:46:44.000000000 +0000 @@ -43,6 +43,7 @@ */ public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL) { + trigger_error(__CLASS__ . ' is deprecated, use Tracy\Debugger.', E_USER_DEPRECATED); parent::enable($mode, $logDirectory, $email); self::$blueScreen = self::getBlueScreen(); self::$bar = self::getBar(); diff -Nru php-nette-2.3.10/Nette/deprecated/Diagnostics/Helpers.php php-nette-2.4-20160731/Nette/deprecated/Diagnostics/Helpers.php --- php-nette-2.3.10/Nette/deprecated/Diagnostics/Helpers.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Diagnostics/Helpers.php 2016-07-31 17:46:44.000000000 +0000 @@ -23,6 +23,7 @@ */ public static function editorLink($file, $line = NULL) { + trigger_error(__METHOD__ . '() is deprecated; use Tracy\Helpers::editorLink() instead.', E_USER_DEPRECATED); if (Debugger::$editor && is_file($file)) { $dir = dirname(strtr($file, '/', DIRECTORY_SEPARATOR)); $base = isset($_SERVER['SCRIPT_FILENAME']) ? dirname(dirname(strtr($_SERVER['SCRIPT_FILENAME'], '/', DIRECTORY_SEPARATOR))) : dirname($dir); diff -Nru php-nette-2.3.10/Nette/deprecated/Environment.php php-nette-2.4-20160731/Nette/deprecated/Environment.php --- php-nette-2.3.10/Nette/deprecated/Environment.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Environment.php 2016-07-31 17:46:44.000000000 +0000 @@ -49,6 +49,7 @@ */ public static function isConsole() { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); return PHP_SAPI === 'cli'; } @@ -59,6 +60,7 @@ */ public static function isProduction() { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if (self::$productionMode === NULL) { self::$productionMode = !Nette\Configurator::detectDebugMode(); } @@ -73,6 +75,7 @@ */ public static function setProductionMode($value = TRUE) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); self::$productionMode = (bool) $value; } @@ -89,6 +92,7 @@ */ public static function setVariable($name, $value, $expand = TRUE) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if ($expand && is_string($value)) { $value = self::getContext()->expand($value); } @@ -105,6 +109,7 @@ */ public static function getVariable($name, $default = NULL) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if (isset(self::getContext()->parameters[$name])) { return self::getContext()->parameters[$name]; } elseif (func_num_args() > 1) { @@ -121,6 +126,7 @@ */ public static function getVariables() { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); return self::getContext()->parameters; } @@ -133,6 +139,7 @@ */ public static function expand($s) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); return self::getContext()->expand($s); } @@ -159,6 +166,7 @@ */ public static function getContext() { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if (self::$context === NULL) { self::loadConfig(); } @@ -284,6 +292,7 @@ */ public static function loadConfig($file = NULL, $section = NULL) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if (self::$createdAt) { throw new Nette\InvalidStateException('Nette\Configurator has already been created automatically by Nette\Environment at ' . self::$createdAt); } elseif (!defined('TEMP_DIR')) { @@ -318,6 +327,7 @@ */ public static function getConfig($key = NULL, $default = NULL) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); $params = Nette\Utils\ArrayHash::from(self::getContext()->parameters); if (func_num_args()) { return isset($params[$key]) ? $params[$key] : $default; diff -Nru php-nette-2.3.10/Nette/deprecated/Latte/Engine.php php-nette-2.4-20160731/Nette/deprecated/Latte/Engine.php --- php-nette-2.3.10/Nette/deprecated/Latte/Engine.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Latte/Engine.php 2016-07-31 17:46:44.000000000 +0000 @@ -20,6 +20,10 @@ public function __construct() { + trigger_error(__CLASS__ . ' is deprecated, use Latte\Engine.', E_USER_DEPRECATED); + if (method_exists('Latte\Engine', '__construct')) { + parent::__construct(); + } $this->getParser()->shortNoEscape = TRUE; $this->addFilter('url', 'rawurlencode'); foreach (array('normalize', 'toAscii', 'webalize', 'padLeft', 'padRight', 'reverse') as $name) { diff -Nru php-nette-2.3.10/Nette/deprecated/Templating/FileTemplate.php php-nette-2.4-20160731/Nette/deprecated/Templating/FileTemplate.php --- php-nette-2.3.10/Nette/deprecated/Templating/FileTemplate.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Templating/FileTemplate.php 2016-07-31 17:46:44.000000000 +0000 @@ -28,7 +28,7 @@ */ public function __construct($file = NULL) { - //trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if ($file !== NULL) { $this->setFile($file); } diff -Nru php-nette-2.3.10/Nette/deprecated/Templating/Helpers.php php-nette-2.4-20160731/Nette/deprecated/Templating/Helpers.php --- php-nette-2.3.10/Nette/deprecated/Templating/Helpers.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Templating/Helpers.php 2016-07-31 17:46:44.000000000 +0000 @@ -35,6 +35,7 @@ */ public static function loader($helper) { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if (method_exists(__CLASS__, $helper)) { return array(__CLASS__, $helper); } elseif (isset(self::$helpers[$helper])) { diff -Nru php-nette-2.3.10/Nette/deprecated/Templating/Template.php php-nette-2.4-20160731/Nette/deprecated/Templating/Template.php --- php-nette-2.3.10/Nette/deprecated/Templating/Template.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Templating/Template.php 2016-07-31 17:46:44.000000000 +0000 @@ -43,6 +43,12 @@ private $cacheStorage; + public function __construct() + { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); + } + + /** * Sets template source code. * @param string diff -Nru php-nette-2.3.10/Nette/deprecated/Utils/FreezableObject.php php-nette-2.4-20160731/Nette/deprecated/Utils/FreezableObject.php --- php-nette-2.3.10/Nette/deprecated/Utils/FreezableObject.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/deprecated/Utils/FreezableObject.php 2016-07-31 17:46:44.000000000 +0000 @@ -52,6 +52,7 @@ */ protected function updating() { + trigger_error(__CLASS__ . ' is deprecated.', E_USER_DEPRECATED); if ($this->frozen) { $class = get_class($this); throw new InvalidStateException("Cannot modify a frozen object $class."); diff -Nru php-nette-2.3.10/Nette/DI/CompilerExtension.php php-nette-2.4-20160731/Nette/DI/CompilerExtension.php --- php-nette-2.3.10/Nette/DI/CompilerExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/CompilerExtension.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * Configurator compiling extension. */ -abstract class CompilerExtension extends Nette\Object +abstract class CompilerExtension { + use Nette\SmartObject; + /** @var Compiler */ protected $compiler; @@ -22,7 +24,7 @@ protected $name; /** @var array */ - protected $config = array(); + protected $config = []; public function setCompiler(Compiler $compiler, $name) diff -Nru php-nette-2.3.10/Nette/DI/Compiler.php php-nette-2.4-20160731/Nette/DI/Compiler.php --- php-nette-2.3.10/Nette/DI/Compiler.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Compiler.php 2016-07-31 17:46:34.000000000 +0000 @@ -14,27 +14,33 @@ /** * DI container compiler. */ -class Compiler extends Nette\Object +class Compiler { + use Nette\SmartObject; + /** @var CompilerExtension[] */ - private $extensions = array(); + private $extensions = []; /** @var ContainerBuilder */ private $builder; /** @var array */ - private $config = array(); + private $config = []; + + /** @var DependencyChecker */ + private $dependencies; - /** @var string[] of file names */ - private $dependencies = array(); + /** @var string */ + private $className = 'Container'; /** @var array reserved section names */ - private static $reserved = array('services' => 1, 'parameters' => 1); + private static $reserved = ['services' => 1, 'parameters' => 1]; public function __construct(ContainerBuilder $builder = NULL) { $this->builder = $builder ?: new ContainerBuilder; + $this->dependencies = new DependencyChecker; } @@ -73,6 +79,16 @@ /** + * @return self + */ + public function setClassName($className) + { + $this->className = $className; + return $this; + } + + + /** * Adds new configuration. * @return self */ @@ -91,7 +107,7 @@ { $loader = new Config\Loader; $this->addConfig($loader->load($file)); - $this->addDependencies($loader->getDependencies()); + $this->dependencies->add($loader->getDependencies()); return $this; } @@ -107,39 +123,42 @@ /** - * Adds a files to the list of dependencies. + * Adds dependencies to the list. + * @param array of ReflectionClass|\ReflectionFunctionAbstract|string * @return self */ - public function addDependencies(array $files) + public function addDependencies(array $deps) { - $this->dependencies = array_merge($this->dependencies, $files); + $this->dependencies->add(array_filter($deps)); return $this; } /** - * Returns the unique list of dependent files. + * Exports dependencies. * @return array */ - public function getDependencies() + public function exportDependencies() { - return array_values(array_unique(array_filter($this->dependencies))); + return $this->dependencies->export(); } /** - * @return Nette\PhpGenerator\ClassType[]|string + * @return string */ public function compile(array $config = NULL, $className = NULL, $parentName = NULL) { - $this->config = $config ?: $this->config; + if (func_num_args()) { + trigger_error(__METHOD__ . ' arguments are deprecated, use Compiler::addConfig() and Compiler::setClassName().', E_USER_DEPRECATED); + $this->config = func_get_arg(0) ?: $this->config; + $this->className = @func_get_arg(1) ?: $this->className; + } $this->processParameters(); $this->processExtensions(); $this->processServices(); - $classes = $this->generateCode($className, $parentName); - return func_num_args() - ? implode("\n\n\n", $classes) // back compatiblity - : $classes; + $classes = $this->generateCode(); + return implode("\n\n\n", $classes); } @@ -158,20 +177,17 @@ $this->config = Helpers::expand(array_diff_key($this->config, self::$reserved), $this->builder->parameters) + array_intersect_key($this->config, self::$reserved); - foreach ($first = $this->getExtensions('Nette\DI\Extensions\ExtensionsExtension') as $name => $extension) { - $extension->setConfig(isset($this->config[$name]) ? $this->config[$name] : array()); + foreach ($first = $this->getExtensions(Extensions\ExtensionsExtension::class) as $name => $extension) { + $extension->setConfig(isset($this->config[$name]) ? $this->config[$name] : []); $extension->loadConfiguration(); } - $last = $this->getExtensions('Nette\DI\Extensions\InjectExtension'); + $last = $this->getExtensions(Extensions\InjectExtension::class); $this->extensions = array_merge(array_diff_key($this->extensions, $last), $last); $extensions = array_diff_key($this->extensions, $first); foreach (array_intersect_key($extensions, $this->config) as $name => $extension) { - if (isset($this->config[$name]['services'])) { - trigger_error("Support for inner section 'services' inside extension was removed (used in '$name').", E_USER_DEPRECATED); - } - $extension->setConfig($this->config[$name] ?: array()); + $extension->setConfig($this->config[$name] ?: []); } foreach ($extensions as $extension) { @@ -195,29 +211,31 @@ /** @internal */ public function processServices() { - $this->parseServices($this->builder, $this->config); + if (isset($this->config['services'])) { + self::loadDefinitions($this->builder, $this->config['services']); + } } /** @internal */ - public function generateCode($className, $parentName = NULL) + public function generateCode() { + if (func_num_args()) { + trigger_error(__METHOD__ . ' arguments are deprecated, use Compiler::setClassName().', E_USER_DEPRECATED); + $this->className = func_get_arg(0) ?: $this->className; + } + $this->builder->prepareClassList(); - $state = serialize($this->builder->getDefinitions()); foreach ($this->extensions as $extension) { $extension->beforeCompile(); - $rc = new \ReflectionClass($extension); - $this->dependencies[] = $rc->getFileName(); - if ($state !== serialize($this->builder->getDefinitions())) { - $this->builder->prepareClassList(); - $state = serialize($this->builder->getDefinitions()); - } + $this->dependencies->add([(new \ReflectionClass($extension))->getFileName()]); } - $classes = $this->builder->generateClasses($className, $parentName); + $generator = new PhpGenerator($this->builder); + $classes = $generator->generate($this->className); $classes[0]->addMethod('initialize'); - $this->addDependencies($this->builder->getDependencies()); + $this->dependencies->add($this->builder->getDependencies()); foreach ($this->extensions as $extension) { $extension->afterCompile($classes[0]); @@ -230,22 +248,17 @@ /** - * Parses section 'services' from (unexpanded) configuration file. + * Adds service definitions from configuration. * @return void */ - public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL) + public static function loadDefinitions(ContainerBuilder $builder, array $services, $namespace = NULL) { - if (!empty($config['factories'])) { - throw new Nette\DeprecatedException("Section 'factories' is deprecated, move definitions to section 'services' and append key 'autowired: no'."); - } - - $services = isset($config['services']) ? $config['services'] : array(); - $depths = array(); + $depths = []; foreach ($services as $name => $def) { - $path = array(); + $path = []; while (Config\Helpers::isInheriting($def)) { $path[] = $def; - $def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : array(); + $def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : []; if (in_array($def, $path, TRUE)) { throw new ServiceCreationException("Circular reference detected for service '$name'."); } @@ -254,18 +267,21 @@ } array_multisort($depths, $services); - foreach ($services as $origName => $def) { - if ((string) (int) $origName === (string) $origName) { + foreach ($services as $name => $def) { + if ((string) (int) $name === (string) $name) { $postfix = $def instanceof Statement && is_string($def->getEntity()) ? '.' . $def->getEntity() : (is_scalar($def) ? ".$def" : ''); $name = (count($builder->getDefinitions()) + 1) . preg_replace('#\W+#', '_', $postfix); - } else { - $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_'); + } elseif ($namespace) { + $name = $namespace . '.' . $name; } if ($def === FALSE) { $builder->removeDefinition($name); continue; } + if ($namespace) { + $def = Helpers::prefixServiceName($def, $namespace); + } $params = $builder->parameters; if (is_array($def) && isset($def['parameters'])) { @@ -277,10 +293,11 @@ $def = Helpers::expand($def, $params); if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) { + trigger_error("Section inheritance $name < $parent is deprecated.", E_USER_DEPRECATED); $builder->removeDefinition($name); $definition = $builder->addDefinition( $name, - $parent === Config\Helpers::OVERWRITE ? NULL : unserialize(serialize($builder->getDefinition($parent))) // deep clone + $parent === Config\Helpers::OVERWRITE ? NULL : clone $builder->getDefinition($parent) ); } elseif ($builder->hasDefinition($name)) { $definition = $builder->getDefinition($name); @@ -289,61 +306,65 @@ } try { - static::parseService($definition, $def); + static::loadDefinition($definition, $def); } catch (\Exception $e) { - throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e); - } - - if ($definition->getClass() === 'self' || ($definition->getFactory() && $definition->getFactory()->getEntity() === 'self')) { - throw new Nette\DeprecatedException("Replace service definition '$origName: self' with '- $origName'."); + throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); } } } /** - * Parses single service from configuration file. + * Parses single service definition from configuration. * @return void */ - public static function parseService(ServiceDefinition $definition, $config) + public static function loadDefinition(ServiceDefinition $definition, $config) { if ($config === NULL) { return; } elseif (is_string($config) && interface_exists($config)) { - $config = array('class' => NULL, 'implement' => $config); + $config = ['class' => NULL, 'implement' => $config]; } elseif ($config instanceof Statement && is_string($config->getEntity()) && interface_exists($config->getEntity())) { - $config = array('class' => NULL, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments)); + $config = ['class' => NULL, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments)]; } elseif (!is_array($config) || isset($config[0], $config[1])) { - $config = array('class' => NULL, 'create' => $config); + $config = ['class' => NULL, 'factory' => $config]; } - if (array_key_exists('factory', $config)) { - $config['create'] = $config['factory']; - unset($config['factory']); - }; + if (array_key_exists('create', $config)) { + trigger_error("Key 'create' is deprecated, use 'factory' or 'class' in configuration.", E_USER_DEPRECATED); + $config['factory'] = $config['create']; + unset($config['create']); + } - $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'dynamic', 'inject', 'parameters', 'implement', 'run', 'tags'); + $known = ['class', 'factory', 'arguments', 'setup', 'autowired', 'dynamic', 'inject', 'parameters', 'implement', 'run', 'tags']; if ($error = array_diff(array_keys($config), $known)) { - throw new Nette\InvalidStateException(sprintf("Unknown or deprecated key '%s' in definition of service.", implode("', '", $error))); + $hints = array_filter(array_map(function ($error) use ($known) { + return Nette\Utils\ObjectMixin::getSuggestion($known, $error); + }, $error)); + $hint = $hints ? ", did you mean '" . implode("', '", $hints) . "'?" : '.'; + throw new Nette\InvalidStateException(sprintf("Unknown key '%s' in definition of service$hint", implode("', '", $error))); } - $config = self::filterArguments($config); + $config = Helpers::filterArguments($config); - $arguments = array(); + if (array_key_exists('class', $config) || array_key_exists('factory', $config)) { + $definition->setClass(NULL); + $definition->setFactory(NULL); + } + + $arguments = []; if (array_key_exists('arguments', $config)) { Validators::assertField($config, 'arguments', 'array'); $arguments = $config['arguments']; + if (!Config\Helpers::takeParent($arguments) && !Nette\Utils\Arrays::isList($arguments) && $definition->getFactory()) { + $arguments += $definition->getFactory()->arguments; + } $definition->setArguments($arguments); } - if (array_key_exists('class', $config) || array_key_exists('create', $config)) { - $definition->setClass(NULL); - $definition->setFactory(NULL); - } - if (array_key_exists('class', $config)) { Validators::assertField($config, 'class', 'string|Nette\DI\Statement|null'); if (!$config['class'] instanceof Statement) { @@ -352,18 +373,21 @@ $definition->setFactory($config['class'], $arguments); } - if (array_key_exists('create', $config)) { - Validators::assertField($config, 'create', 'callable|Nette\DI\Statement|null'); - $definition->setFactory($config['create'], $arguments); + if (array_key_exists('factory', $config)) { + Validators::assertField($config, 'factory', 'callable|Nette\DI\Statement|null'); + $definition->setFactory($config['factory'], $arguments); } if (isset($config['setup'])) { if (Config\Helpers::takeParent($config['setup'])) { - $definition->setSetup(array()); + $definition->setSetup([]); } Validators::assertField($config, 'setup', 'list'); foreach ($config['setup'] as $id => $setup) { - Validators::assert($setup, 'callable|Nette\DI\Statement', "setup item #$id"); + Validators::assert($setup, 'callable|Nette\DI\Statement|array:1', "setup item #$id"); + if (is_array($setup)) { + $setup = new Statement(key($setup), array_values($setup)); + } $definition->addSetup($setup); } } @@ -380,7 +404,7 @@ } if (isset($config['autowired'])) { - Validators::assertField($config, 'autowired', 'bool'); + Validators::assertField($config, 'autowired', 'bool|string|array'); $definition->setAutowired($config['autowired']); } @@ -395,13 +419,14 @@ } if (isset($config['run'])) { + trigger_error("Option 'run' is deprecated, use 'run' as tag.", E_USER_DEPRECATED); $config['tags']['run'] = (bool) $config['run']; } if (isset($config['tags'])) { Validators::assertField($config, 'tags', 'array'); if (Config\Helpers::takeParent($config['tags'])) { - $definition->setTags(array()); + $definition->setTags([]); } foreach ($config['tags'] as $tag => $attrs) { if (is_int($tag) && is_string($attrs)) { @@ -414,25 +439,24 @@ } - /** - * Removes ... and process constants recursively. - * @return array - */ + /** @deprecated */ public static function filterArguments(array $args) { - foreach ($args as $k => $v) { - if ($v === '...') { - unset($args[$k]); - } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) { - $args[$k] = ContainerBuilder::literal(ltrim($v, ':')); - } elseif (is_array($v)) { - $args[$k] = self::filterArguments($v); - } elseif ($v instanceof Statement) { - $tmp = self::filterArguments(array($v->getEntity())); - $args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments)); - } - } - return $args; + return Helpers::filterArguments($args); + } + + + /** @deprecated */ + public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL) + { + self::loadDefinitions($builder, isset($config['services']) ? $config['services'] : [], $namespace); + } + + + /** @deprecated */ + public static function parseService(ServiceDefinition $definition, $config) + { + self::loadDefinition($definition, $config); } } diff -Nru php-nette-2.3.10/Nette/DI/Config/Adapters/IniAdapter.php php-nette-2.4-20160731/Nette/DI/Config/Adapters/IniAdapter.php --- php-nette-2.3.10/Nette/DI/Config/Adapters/IniAdapter.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Config/Adapters/IniAdapter.php 2016-07-31 17:46:34.000000000 +0000 @@ -14,8 +14,10 @@ /** * Reading and generating INI files. */ -class IniAdapter extends Nette\Object implements Nette\DI\Config\IAdapter +class IniAdapter implements Nette\DI\Config\IAdapter { + use Nette\SmartObject; + /** @internal */ const INHERITING_SEPARATOR = '<', // child < parent KEY_SEPARATOR = '.', // key nesting key1.key2.key3 @@ -37,13 +39,13 @@ throw new Nette\InvalidStateException("parse_ini_file(): $error[message]"); } - $data = array(); + $data = []; foreach ($ini as $secName => $secData) { if (is_array($secData)) { // is section? if (substr($secName, -1) === self::RAW_SECTION) { $secName = substr($secName, 0, -1); } else { // process key nesting separator (key1.key2.key3) - $tmp = array(); + $tmp = []; foreach ($secData as $key => $val) { $cursor = & $tmp; $key = str_replace(self::ESCAPED_KEY_SEPARATOR, "\xFF", $key); @@ -93,10 +95,10 @@ */ public function dump(array $data) { - $output = array(); + $output = []; foreach ($data as $name => $secData) { if (!is_array($secData)) { - $output = array(); + $output = []; self::build($data, $output, ''); break; } diff -Nru php-nette-2.3.10/Nette/DI/Config/Adapters/NeonAdapter.php php-nette-2.4-20160731/Nette/DI/Config/Adapters/NeonAdapter.php --- php-nette-2.3.10/Nette/DI/Config/Adapters/NeonAdapter.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Config/Adapters/NeonAdapter.php 2016-07-31 17:46:34.000000000 +0000 @@ -16,8 +16,10 @@ /** * Reading and generating NEON files. */ -class NeonAdapter extends Nette\Object implements Nette\DI\Config\IAdapter +class NeonAdapter implements Nette\DI\Config\IAdapter { + use Nette\SmartObject; + /** @internal */ const INHERITING_SEPARATOR = '<', // child < parent PREVENT_MERGING = '!'; @@ -35,16 +37,16 @@ private function process(array $arr) { - $res = array(); + $res = []; foreach ($arr as $key => $val) { - if (substr($key, -1) === self::PREVENT_MERGING) { + if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING) { if (!is_array($val) && $val !== NULL) { throw new Nette\InvalidStateException("Replacing operator is available only for arrays, item '$key' is not array."); } $key = substr($key, 0, -1); $val[Helpers::EXTENDS_KEY] = Helpers::OVERWRITE; - } elseif (preg_match('#^(\S+)\s+' . self::INHERITING_SEPARATOR . '\s+(\S+)\z#', $key, $matches)) { + } elseif (is_string($key) && preg_match('#^(\S+)\s+' . self::INHERITING_SEPARATOR . '\s+(\S+)\z#', $key, $matches)) { if (!is_array($val) && $val !== NULL) { throw new Nette\InvalidStateException("Inheritance operator is available only for arrays, item '$key' is not array."); } @@ -62,13 +64,13 @@ $tmp = NULL; foreach ($this->process($val->attributes) as $st) { $tmp = new Statement( - $tmp === NULL ? $st->getEntity() : array($tmp, ltrim($st->getEntity(), ':')), + $tmp === NULL ? $st->getEntity() : [$tmp, ltrim($st->getEntity(), ':')], $st->arguments ); } $val = $tmp; } else { - $tmp = $this->process(array($val->value)); + $tmp = $this->process([$val->value]); $val = new Statement($tmp[0], $this->process($val->attributes)); } } @@ -84,7 +86,7 @@ */ public function dump(array $data) { - $tmp = array(); + $tmp = []; foreach ($data as $name => $secData) { if ($parent = Helpers::takeParent($secData)) { $name .= ' ' . self::INHERITING_SEPARATOR . ' ' . $parent; @@ -95,7 +97,7 @@ $tmp, function (& $val) { if ($val instanceof Statement) { - $val = NeonAdapter::statementToEntity($val); + $val = self::statementToEntity($val); } } ); @@ -106,28 +108,27 @@ /** * @return Neon\Entity - * @internal */ - public static function statementToEntity(Statement $val) + private static function statementToEntity(Statement $val) { array_walk_recursive( $val->arguments, function (& $val) { if ($val instanceof Statement) { - $val = NeonAdapter::statementToEntity($val); + $val = self::statementToEntity($val); } } ); - if (is_array($val->entity) && $val->entity[0] instanceof Statement) { + if (is_array($val->getEntity()) && $val->getEntity()[0] instanceof Statement) { return new Neon\Entity( Neon\Neon::CHAIN, - array( - self::statementToEntity($val->entity[0]), - new Neon\Entity('::' . $val->entity[1], $val->arguments) - ) + [ + self::statementToEntity($val->getEntity()[0]), + new Neon\Entity('::' . $val->getEntity()[1], $val->arguments) + ] ); } else { - return new Neon\Entity($val->entity, $val->arguments); + return new Neon\Entity($val->getEntity(), $val->arguments); } } diff -Nru php-nette-2.3.10/Nette/DI/Config/Adapters/PhpAdapter.php php-nette-2.4-20160731/Nette/DI/Config/Adapters/PhpAdapter.php --- php-nette-2.3.10/Nette/DI/Config/Adapters/PhpAdapter.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Config/Adapters/PhpAdapter.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,9 @@ /** * Reading and generating PHP files. */ -class PhpAdapter extends Nette\Object implements Nette\DI\Config\IAdapter +class PhpAdapter implements Nette\DI\Config\IAdapter { + use Nette\SmartObject; /** * Reads configuration from PHP file. diff -Nru php-nette-2.3.10/Nette/DI/Config/Helpers.php php-nette-2.4-20160731/Nette/DI/Config/Helpers.php --- php-nette-2.3.10/Nette/DI/Config/Helpers.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Config/Helpers.php 2016-07-31 17:46:34.000000000 +0000 @@ -7,15 +7,19 @@ namespace Nette\DI\Config; +use Nette; + /** * Configuration helpers. */ class Helpers { - const EXTENDS_KEY = '_extends', - OVERWRITE = TRUE; + use Nette\StaticClass; + const + EXTENDS_KEY = '_extends', + OVERWRITE = TRUE; /** * Merges configurations. Left has higher priority than right one. diff -Nru php-nette-2.3.10/Nette/DI/Config/Loader.php php-nette-2.4-20160731/Nette/DI/Config/Loader.php --- php-nette-2.3.10/Nette/DI/Config/Loader.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Config/Loader.php 2016-07-31 17:46:34.000000000 +0000 @@ -14,18 +14,20 @@ /** * Configuration file loader. */ -class Loader extends Nette\Object +class Loader { + use Nette\SmartObject; + /** @internal */ const INCLUDES_KEY = 'includes'; - private $adapters = array( - 'php' => 'Nette\DI\Config\Adapters\PhpAdapter', - 'ini' => 'Nette\DI\Config\Adapters\IniAdapter', - 'neon' => 'Nette\DI\Config\Adapters\NeonAdapter', - ); + private $adapters = [ + 'php' => Adapters\PhpAdapter::class, + 'ini' => Adapters\IniAdapter::class, + 'neon' => Adapters\NeonAdapter::class, + ]; - private $dependencies = array(); + private $dependencies = []; /** @@ -39,7 +41,7 @@ if (!is_file($file) || !is_readable($file)) { throw new Nette\FileNotFoundException("File '$file' is missing or is not readable."); } - $this->dependencies[] = realpath($file); + $this->dependencies[] = $file; $data = $this->getAdapter($file)->load($file); if ($section) { @@ -50,7 +52,7 @@ } // include child files - $merged = array(); + $merged = []; if (isset($data[self::INCLUDES_KEY])) { Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'"); foreach ($data[self::INCLUDES_KEY] as $include) { diff -Nru php-nette-2.3.10/Nette/DI/ContainerBuilder.php php-nette-2.4-20160731/Nette/DI/ContainerBuilder.php --- php-nette-2.3.10/Nette/DI/ContainerBuilder.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/ContainerBuilder.php 2016-07-31 17:46:34.000000000 +0000 @@ -8,46 +8,45 @@ namespace Nette\DI; use Nette; -use Nette\Utils\Validators; -use Nette\Utils\Strings; use Nette\PhpGenerator\Helpers as PhpHelpers; +use Nette\Utils\Strings; +use Nette\Utils\Validators; use ReflectionClass; /** - * Basic container builder. + * Container builder. */ -class ContainerBuilder extends Nette\Object +class ContainerBuilder { + use Nette\SmartObject; + const THIS_SERVICE = 'self', THIS_CONTAINER = 'container'; /** @var array */ - public $parameters = array(); - - /** @var string */ - private $className = 'Container'; + public $parameters = []; /** @var ServiceDefinition[] */ - private $definitions = array(); + private $definitions = []; /** @var array of alias => service */ - private $aliases = array(); + private $aliases = []; /** @var array for auto-wiring */ - private $classes; + private $classList = []; - /** @var string[] of classes excluded from auto-wiring */ - private $excludedClasses = array(); + /** @var bool */ + private $classListNeedsRefresh = TRUE; - /** @var array of file names */ - private $dependencies = array(); + /** @var string[] of classes excluded from auto-wiring */ + private $excludedClasses = []; - /** @var Nette\PhpGenerator\ClassType[] */ - private $generatedClasses = array(); + /** @var array */ + private $dependencies = []; /** @var string */ - /*private in 5.4*/public $currentService; + private $currentService; /** @@ -57,6 +56,7 @@ */ public function addDefinition($name, ServiceDefinition $definition = NULL) { + $this->classListNeedsRefresh = TRUE; if (!is_string($name) || !$name) { // builder is not ready for falsy names such as '0' throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($name))); } @@ -64,7 +64,13 @@ if (isset($this->definitions[$name])) { throw new Nette\InvalidStateException("Service '$name' has already been added."); } - return $this->definitions[$name] = $definition ?: new ServiceDefinition; + if (!$definition) { + $definition = new ServiceDefinition; + } + $definition->setNotifier(function () { + $this->classListNeedsRefresh = TRUE; + }); + return $this->definitions[$name] = $definition; } @@ -75,16 +81,9 @@ */ public function removeDefinition($name) { + $this->classListNeedsRefresh = TRUE; $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; unset($this->definitions[$name]); - - if ($this->classes) { - foreach ($this->classes as & $tmp) { - foreach ($tmp as & $names) { - $names = array_values(array_diff($names, array($name))); - } - } - } } @@ -169,21 +168,37 @@ /** + * @param string[] * @return self */ + public function addExcludedClasses(array $classes) + { + foreach ($classes as $class) { + if (class_exists($class) || interface_exists($class)) { + self::checkCase($class); + $this->excludedClasses += class_parents($class) + class_implements($class) + [$class => $class]; + } + } + return $this; + } + + + /** + * @deprecated + */ public function setClassName($name) { - $this->className = (string) $name; + trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED); return $this; } /** - * @return string + * @deprecated */ public function getClassName() { - return $this->className; + trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED); } @@ -200,22 +215,26 @@ { $class = ltrim($class, '\\'); - if ($this->currentService !== NULL) { - $curClass = $this->definitions[$this->currentService]->getClass(); - if ($curClass === $class || is_subclass_of($curClass, $class)) { - return $this->currentService; - } + if ($this->currentService !== NULL + && is_a($this->definitions[$this->currentService]->getClass(), $class, TRUE) + ) { + return $this->currentService; } - if (empty($this->classes[$class][TRUE])) { + $classes = $this->getClassList(); + if (empty($classes[$class][TRUE])) { self::checkCase($class); return; - } elseif (count($this->classes[$class][TRUE]) === 1) { - return $this->classes[$class][TRUE][0]; + } elseif (count($classes[$class][TRUE]) === 1) { + return $classes[$class][TRUE][0]; } else { - throw new ServiceCreationException("Multiple services of type $class found: " . implode(', ', $this->classes[$class][TRUE])); + $list = $classes[$class][TRUE]; + $hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.')) + ? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.' + : ''; + throw new ServiceCreationException("Multiple services of type $class found: " . implode(', ', $list) . $hint); } } @@ -229,9 +248,10 @@ { $class = ltrim($class, '\\'); self::checkCase($class); - $found = array(); - if (!empty($this->classes[$class])) { - foreach (call_user_func_array('array_merge', $this->classes[$class]) as $name) { + $found = []; + $classes = $this->getClassList(); + if (!empty($classes[$class])) { + foreach (array_merge(...array_values($classes[$class])) as $name) { $found[$name] = $this->definitions[$name]; } } @@ -246,7 +266,7 @@ */ public function findByTag($tag) { - $found = array(); + $found = []; foreach ($this->definitions as $name => $def) { if (($tmp = $def->getTag($tag)) !== NULL) { $found[$name] = $tmp; @@ -257,25 +277,15 @@ /** - * Creates a list of arguments using autowiring. - * @return array + * @internal */ - public function autowireArguments($class, $method, array $arguments) + public function getClassList() { - $rc = new ReflectionClass($class); - if (!$rc->hasMethod($method)) { - if (!Nette\Utils\Arrays::isList($arguments)) { - throw new ServiceCreationException("Unable to pass specified arguments to $class::$method()."); - } - return $arguments; + if ($this->classList !== FALSE && $this->classListNeedsRefresh) { + $this->prepareClassList(); + $this->classListNeedsRefresh = FALSE; } - - $rm = $rc->getMethod($method); - if (!$rm->isPublic()) { - throw new ServiceCreationException("$class::$method() is not callable."); - } - $this->addDependency($rm->getFileName()); - return Helpers::autowireArguments($rm, $arguments, $this); + return $this->classList ?: []; } @@ -287,9 +297,9 @@ public function prepareClassList() { unset($this->definitions[self::THIS_CONTAINER]); - $this->addDefinition(self::THIS_CONTAINER)->setClass('Nette\DI\Container'); + $this->addDefinition(self::THIS_CONTAINER)->setClass(Container::class); - $this->classes = FALSE; + $this->classList = FALSE; foreach ($this->definitions as $name => $def) { // prepare generated factories @@ -310,7 +320,7 @@ if (!$def->getClass()) { throw new ServiceCreationException("Class and factory are missing in definition of service '$name'."); } - $def->setFactory($def->getClass(), ($factory = $def->getFactory()) ? $factory->arguments : array()); + $def->setFactory($def->getClass(), ($factory = $def->getFactory()) ? $factory->arguments : []); } // auto-disable autowiring for aliases @@ -327,24 +337,40 @@ } // build auto-wiring list - $excludedClasses = array(); - foreach ($this->excludedClasses as $class) { - self::checkCase($class); - $excludedClasses += class_parents($class) + class_implements($class) + array($class => $class); - } - - $this->classes = array(); + $this->classList = $preferred = []; foreach ($this->definitions as $name => $def) { if ($class = $def->getImplement() ?: $def->getClass()) { - foreach (class_parents($class) + class_implements($class) + array($class) as $parent) { - $this->classes[$parent][$def->isAutowired() && empty($excludedClasses[$parent])][] = (string) $name; + $defAutowired = $def->getAutowired(); + if (is_array($defAutowired)) { + foreach ($defAutowired as $k => $aclass) { + if ($aclass === self::THIS_SERVICE) { + $defAutowired[$k] = $class; + } elseif (!is_a($class, $aclass, TRUE)) { + throw new ServiceCreationException("Incompatible class $aclass in autowiring definition of service '$name'."); + } + } } - } - } - foreach ($this->classes as $class => $foo) { - $rc = new ReflectionClass($class); - $this->addDependency($rc->getFileName()); + foreach (class_parents($class) + class_implements($class) + [$class] as $parent) { + $autowired = $defAutowired && empty($this->excludedClasses[$parent]); + if ($autowired && is_array($defAutowired)) { + $autowired = FALSE; + foreach ($defAutowired as $aclass) { + if (is_a($parent, $aclass, TRUE)) { + if (empty($preferred[$parent]) && isset($this->classList[$parent][TRUE])) { + $this->classList[$parent][FALSE] = array_merge(...$this->classList[$parent]); + $this->classList[$parent][TRUE] = []; + } + $preferred[$parent] = $autowired = TRUE; + break; + } + } + } elseif (isset($preferred[$parent])) { + $autowired = FALSE; + } + $this->classList[$parent][$autowired][] = (string) $name; + } + } } } @@ -357,6 +383,7 @@ } self::checkCase($interface); $rc = new ReflectionClass($interface); + $this->addDependency($rc); $method = $rc->hasMethod('create') ? $rc->getMethod('create') : ($rc->hasMethod('get') ? $rc->getMethod('get') : NULL); @@ -364,7 +391,7 @@ if (count($rc->getMethods()) !== 1 || !$method || $method->isStatic()) { throw new ServiceCreationException("Interface $interface used in service '$name' must have just one non-static method create() or get()."); } - $def->setImplementType($methodName = $rc->hasMethod('create') ? 'create' : 'get'); + $def->setImplementMode($methodName = $rc->hasMethod('create') ? $def::IMPLEMENT_MODE_CREATE : $def::IMPLEMENT_MODE_GET); if (!$def->getClass() && !$def->getEntity()) { $returnType = PhpReflection::getReturnType($method); @@ -388,12 +415,12 @@ } if (!$def->parameters) { - $ctorParams = array(); + $ctorParams = []; if (!$def->getEntity()) { - $def->setFactory($def->getClass(), $def->getFactory() ? $def->getFactory()->arguments : array()); + $def->setFactory($def->getClass(), $def->getFactory() ? $def->getFactory()->arguments : []); } - if (($class = $this->resolveEntityClass($def->getFactory(), array($name => 1))) - && ($rc = new ReflectionClass($class)) && ($ctor = $rc->getConstructor()) + if (($class = $this->resolveEntityClass($def->getFactory(), [$name => 1])) + && ($ctor = (new ReflectionClass($class))->getConstructor()) ) { foreach ($ctor->getParameters() as $param) { $ctorParams[$param->getName()] = $param; @@ -413,7 +440,7 @@ throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $interface::$methodName()" . ($hint ? ", did you mean \${$hint}?" : '.')); } $paramDef = $hint . ' ' . $param->getName(); - if ($param->isOptional()) { + if ($param->isDefaultValueAvailable()) { $def->parameters[$paramDef] = $param->getDefaultValue(); } else { $def->parameters[] = $paramDef; @@ -424,7 +451,7 @@ /** @return string|NULL */ - private function resolveServiceClass($name, $recursive = array()) + private function resolveServiceClass($name, $recursive = []) { if (isset($recursive[$name])) { throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($recursive)))); @@ -432,25 +459,29 @@ $recursive[$name] = TRUE; $def = $this->definitions[$name]; - $class = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL; // call always to check entities - if ($class = $def->getClass() ?: $class) { - $def->setClass($class); + $factoryClass = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL; // call always to check entities + if ($class = $def->getClass() ?: $factoryClass) { if (!class_exists($class) && !interface_exists($class)) { - throw new ServiceCreationException("Type $class used in service '$name' not found or is not class or interface."); + throw new ServiceCreationException("Class or interface '$class' used in service '$name' not found."); } self::checkCase($class); + $def->setClass($class); + if (count($recursive) === 1) { + $this->addDependency(new ReflectionClass($factoryClass ?: $class)); + } - } elseif ($def->isAutowired()) { - trigger_error("Type of service '$name' is unknown.", E_USER_NOTICE); + } elseif ($def->getAutowired()) { + throw new ServiceCreationException("Unknown type of service '$name', declare return type of factory method (for PHP 5 use annotation @return)"); } return $class; } /** @return string|NULL */ - private function resolveEntityClass($entity, $recursive = array()) + private function resolveEntityClass($entity, $recursive = []) { $entity = $this->normalizeEntity($entity instanceof Statement ? $entity->getEntity() : $entity); + $serviceName = current(array_slice(array_keys($recursive), -1)); if (is_array($entity)) { if (($service = $this->getServiceName($entity[0])) || $entity[0] instanceof Statement) { @@ -469,339 +500,207 @@ } if (isset($e) || ($refClass && (!$reflection->isPublic() - || (PHP_VERSION_ID >= 50400 && $refClass->isTrait() && !$reflection->isStatic()) + || ($refClass->isTrait() && !$reflection->isStatic()) ))) { - $name = array_slice(array_keys($recursive), -1); - throw new ServiceCreationException(sprintf("Factory '%s' used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $name[0])); + throw new ServiceCreationException(sprintf("Method %s() used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $serviceName)); } + $this->addDependency($reflection); - return PhpReflection::getReturnType($reflection); + $type = PhpReflection::getReturnType($reflection); + if ($type && !class_exists($type) && !interface_exists($type)) { + throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() used in service '%s' correct?", $type, Nette\Utils\Callback::toString($entity), $serviceName)); + } + return $type; } elseif ($service = $this->getServiceName($entity)) { // alias or factory if (Strings::contains($service, '\\')) { // @\Class return ltrim($service, '\\'); } - return $this->definitions[$service]->getImplement() ?: $this->resolveServiceClass($service, $recursive); - - } elseif (is_string($entity)) { - if (!class_exists($entity) || !($rc = new ReflectionClass($entity)) || !$rc->isInstantiable()) { - $name = array_slice(array_keys($recursive), -1); - throw new ServiceCreationException("Class $entity used in service '$name[0]' not found or is not instantiable."); + return $this->definitions[$service]->getImplement() + ?: $this->definitions[$service]->getClass() + ?: $this->resolveServiceClass($service, $recursive); + + } elseif (is_string($entity)) { // class + if (!class_exists($entity)) { + throw new ServiceCreationException("Class $entity used in service '$serviceName' not found."); } return ltrim($entity, '\\'); } } - private function checkCase($class) - { - if ((class_exists($class) || interface_exists($class)) && ($rc = new ReflectionClass($class)) && $class !== $rc->getName()) { - throw new ServiceCreationException("Case mismatch on class name '$class', correct name is '{$rc->getName()}'."); - } - } - - /** - * @param string[] - * @return self - */ - public function addExcludedClasses(array $classes) - { - $this->excludedClasses = array_merge($this->excludedClasses, $classes); - return $this; - } - - - /** - * Adds a file to the list of dependencies. - * @return self - * @internal - */ - public function addDependency($file) - { - $this->dependencies[$file] = TRUE; - return $this; - } - - - /** - * Returns the list of dependent files. - * @return array - */ - public function getDependencies() - { - unset($this->dependencies[FALSE]); - return array_keys($this->dependencies); - } - - - /********************* code generator ****************d*g**/ - - - /** - * Generates PHP classes. First class is the container. - * @return Nette\PhpGenerator\ClassType[] + * @return void */ - public function generateClasses($className = NULL, $parentName = NULL) + public function complete() { $this->prepareClassList(); - $this->generatedClasses = array(); - $this->className = $className ?: $this->className; - $containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className); - $containerClass->setExtends($parentName ?: 'Nette\DI\Container'); - $containerClass->addMethod('__construct') - ->addBody('parent::__construct(?);', array($this->parameters)); - - $definitions = $this->definitions; - ksort($definitions); - - $meta = $containerClass->addProperty('meta') - ->setVisibility('protected') - ->setValue(array(Container::TYPES => $this->classes)); - - foreach ($definitions as $name => $def) { - $meta->value[Container::SERVICES][$name] = $def->getClass() ?: NULL; - foreach ($def->getTags() as $tag => $value) { - $meta->value[Container::TAGS][$tag][$name] = $value; - } - } - - foreach ($definitions as $name => $def) { - try { - $name = (string) $name; - $methodName = Container::getMethodName($name); - if (!PhpHelpers::isIdentifier($methodName)) { - throw new ServiceCreationException('Name contains invalid characters.'); - } - $containerClass->addMethod($methodName) - ->addDocument('@return ' . ($def->getImplement() ?: $def->getClass())) - ->setBody($name === self::THIS_CONTAINER ? 'return $this;' : $this->generateService($name)) - ->setParameters($def->getImplement() ? array() : $this->convertParameters($def->parameters)); - } catch (\Exception $e) { - throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e); - } - } - - $aliases = $this->aliases; - ksort($aliases); - $meta->value[Container::ALIASES] = $aliases; - - return $this->generatedClasses; - } - - - /** - * Generates body of service method. - * @return string - */ - private function generateService($name) - { - $this->currentService = NULL; - $def = $this->definitions[$name]; - - if ($def->isDynamic()) { - return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);', - array("Unable to create dynamic service '$name', it must be added using addService()") - ); - } - - $entity = $def->getFactory()->getEntity(); - $serviceRef = $this->getServiceName($entity); - $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementType() !== 'create' - ? new Statement(array('@' . self::THIS_CONTAINER, 'getService'), array($serviceRef)) - : $def->getFactory(); - - $code = '$service = ' . $this->formatStatement($factory) . ";\n"; - $this->currentService = $name; - - if (($class = $def->getClass()) && !$serviceRef && $class !== $entity - && !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $class)) - ) { - $code .= PhpHelpers::formatArgs("if (!\$service instanceof $class) {\n" - . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n", - array("Unable to create service '$name', value returned by factory is not $class type.") - ); - } - - foreach ($def->getSetup() as $setup) { - if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) { // auto-prepend @self - $setup->setEntity(array('@self', $setup->getEntity())); + foreach ($this->definitions as $name => $def) { + if ($def->isDynamic()) { + continue; } - $code .= $this->formatStatement($setup) . ";\n"; - } - $this->currentService = NULL; - - $code .= 'return $service;'; - - if (!$def->getImplement()) { - return $code; - } - - $factoryClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType; - $factoryClass->setName(str_replace(array('\\', '.'), '_', "{$this->className}_{$def->getImplement()}Impl_{$name}")) - ->addImplement($def->getImplement()) - ->setFinal(TRUE); - $factoryClass->addProperty('container') - ->setVisibility('private'); + $this->currentService = NULL; + $entity = $def->getFactory()->getEntity(); + $serviceRef = $this->getServiceName($entity); + $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE + ? new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$serviceRef]) + : $def->getFactory(); - $factoryClass->addMethod('__construct') - ->addBody('$this->container = $container;') - ->addParameter('container') - ->setTypeHint($this->className); - - $factoryClass->addMethod($def->getImplementType()) - ->setParameters($this->convertParameters($def->parameters)) - ->setBody(str_replace('$this', '$this->container', $code)) - ->setReturnType(PHP_VERSION_ID >= 70000 ? $def->getClass() : NULL); + try { + $def->setFactory($this->completeStatement($factory)); + $this->classListNeedsRefresh = FALSE; - return "return new {$factoryClass->getName()}(\$this);"; - } + $this->currentService = $name; + $setups = $def->getSetup(); + foreach ($setups as & $setup) { + if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) { // auto-prepend @self + $setup = new Statement(['@' . $name, $setup->getEntity()], $setup->arguments); + } + $setup = $this->completeStatement($setup); + } + $def->setSetup($setups); + } catch (\Exception $e) { + throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); - /** - * Converts parameters from ServiceDefinition to PhpGenerator. - * @return Nette\PhpGenerator\Parameter[] - */ - private function convertParameters(array $parameters) - { - $res = array(); - foreach ($parameters as $k => $v) { - $tmp = explode(' ', is_int($k) ? $v : $k); - $param = $res[] = new Nette\PhpGenerator\Parameter; - $param->setName(end($tmp)); - if (!is_int($k)) { - $param = $param->setOptional(TRUE)->setDefaultValue($v); - } - if (isset($tmp[1])) { - $param->setTypeHint($tmp[0]); + } finally { + $this->currentService = NULL; } } - return $res; } /** - * Formats PHP code for class instantiating, function calling or property setting in PHP. - * @return string - * @internal + * @return Statement */ - public function formatStatement(Statement $statement) + public function completeStatement(Statement $statement) { $entity = $this->normalizeEntity($statement->getEntity()); $arguments = $statement->arguments; if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal - return $this->formatPhp($entity, $arguments); } elseif ($service = $this->getServiceName($entity)) { // factory calling - $params = array(); + $params = []; foreach ($this->definitions[$service]->parameters as $k => $v) { $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v)); } $rm = new \ReflectionFunction(create_function(implode(', ', $params), '')); $arguments = Helpers::autowireArguments($rm, $arguments, $this); - return $this->formatPhp('$this->?(?*)', array(Container::getMethodName($service), $arguments)); + $entity = '@' . $service; } elseif ($entity === 'not') { // operator - return $this->formatPhp('!?', array($arguments[0])); } elseif (is_string($entity)) { // class name - $rc = new ReflectionClass($entity); - if ($constructor = $rc->getConstructor()) { - $this->addDependency($constructor->getFileName()); + if (!class_exists($entity)) { + throw new ServiceCreationException("Class $entity not found."); + } elseif ((new ReflectionClass($entity))->isAbstract()) { + throw new ServiceCreationException("Class $entity is abstract."); + } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== NULL && !$rm->isPublic()) { + $visibility = $rm->isProtected() ? 'protected' : 'private'; + throw new ServiceCreationException("Class $entity has $visibility constructor."); + } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) { + $this->addDependency($constructor); $arguments = Helpers::autowireArguments($constructor, $arguments, $this); } elseif ($arguments) { throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor."); } - return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), array($arguments)); } elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) { throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity))); - } elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '\z#', $entity[1])) { + } elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '(\[\])?\z#', $entity[1])) { throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given."); } elseif ($entity[0] === '') { // globalFunc - return $this->formatPhp("$entity[1](?*)", array($arguments)); - - } elseif ($entity[0] instanceof Statement) { - $inner = $this->formatPhp('?', array($entity[0])); - if (substr($inner, 0, 4) === 'new ') { - $inner = PHP_VERSION_ID < 50400 ? "current(array($inner))" : "($inner)"; - } - return $this->formatPhp("$inner->?(?*)", array($entity[1], $arguments)); - - } elseif (Strings::contains($entity[1], '$')) { // property setter - Validators::assert($arguments, 'list:1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'"); - if ($this->getServiceName($entity[0])) { - return $this->formatPhp('?->? = ?', array($entity[0], substr($entity[1], 1), $arguments[0])); - } else { - return $this->formatPhp($entity[0] . '::$? = ?', array(substr($entity[1], 1), $arguments[0])); + if (!Nette\Utils\Arrays::isList($arguments)) { + throw new ServiceCreationException("Unable to pass specified arguments to $entity[0]."); + } elseif (!function_exists($entity[1])) { + throw new ServiceCreationException("Function $entity[1] doesn't exist."); } - } elseif ($service = $this->getServiceName($entity[0])) { // service method - $class = $this->definitions[$service]->getImplement(); - if (!$class || !method_exists($class, $entity[1])) { - $class = $this->definitions[$service]->getClass(); + $rf = new \ReflectionFunction($entity[1]); + $this->addDependency($rf); + $arguments = Helpers::autowireArguments($rf, $arguments, $this); + + } else { + if ($entity[0] instanceof Statement) { + $entity[0] = $this->completeStatement($entity[0]); + } elseif ($service = $this->getServiceName($entity[0])) { // service method + $entity[0] = '@' . $service; } - if ($class) { + + if ($entity[1][0] === '$') { // property getter, setter or appender + Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'"); + if (!$arguments && substr($entity[1], -2) === '[]') { + throw new ServiceCreationException("Missing argument for $entity[1]."); + } + } else { + $class = empty($service) || $entity[1] === 'create' + ? $this->resolveEntityClass($entity[0]) + : $this->definitions[$service]->getClass(); $arguments = $this->autowireArguments($class, $entity[1], $arguments); } - return $this->formatPhp('?->?(?*)', array($entity[0], $entity[1], $arguments)); - - } else { // static method - $arguments = $this->autowireArguments($entity[0], $entity[1], $arguments); - return $this->formatPhp("$entity[0]::$entity[1](?*)", array($arguments)); } - } - - /** - * Formats PHP statement. - * @return string - * @internal - */ - public function formatPhp($statement, $args) - { - $that = $this; - array_walk_recursive($args, function (& $val) use ($that) { + array_walk_recursive($arguments, function (& $val) { if ($val instanceof Statement) { - $val = ContainerBuilder::literal($that->formatStatement($val)); + $val = $this->completeStatement($val); - } elseif ($val === $that) { - $val = ContainerBuilder::literal('$this'); + } elseif ($val === $this) { + trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED); + $val = self::literal('$this'); } elseif ($val instanceof ServiceDefinition) { - $val = '@' . current(array_keys($that->getDefinitions(), $val, TRUE)); - } + $val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE)); - if (!is_string($val)) { - return; - - } elseif (substr($val, 0, 2) === '@@') { - $val = substr($val, 1); - - } elseif (substr($val, 0, 1) === '@' && strlen($val) > 1) { + } elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') { $pair = explode('::', $val, 2); - $name = $that->getServiceName($pair[0]); - if (isset($pair[1]) && preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { - $val = $that->getDefinition($name)->getClass() . '::' . $pair[1]; - } else { - if ($name === ContainerBuilder::THIS_CONTAINER) { - $val = '$this'; - } elseif ($name === $that->currentService) { - $val = '$service'; - } else { - $val = $that->formatStatement(new Statement(array('@' . ContainerBuilder::THIS_CONTAINER, 'getService'), array($name))); - } - $val .= (isset($pair[1]) ? PhpHelpers::formatArgs('->?', array($pair[1])) : ''); + $name = $this->getServiceName($pair[0]); + if (!isset($pair[1])) { // @service + $val = '@' . $name; + } elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT + $val = self::literal($this->getDefinition($name)->getClass() . '::' . $pair[1]); + } else { // @service::property + $val = new Statement(['@' . $name, '$' . $pair[1]]); } - $val = ContainerBuilder::literal($val); } }); - return PhpHelpers::formatArgs($statement, $args); + + return new Statement($entity, $arguments); + } + + + private function checkCase($class) + { + if ((class_exists($class) || interface_exists($class)) && $class !== ($name = (new ReflectionClass($class))->getName())) { + throw new ServiceCreationException("Case mismatch on class name '$class', correct name is '$name'."); + } + } + + + /** + * Adds item to the list of dependencies. + * @param ReflectionClass|\ReflectionFunctionAbstract|string + * @return self + * @internal + */ + public function addDependency($dep) + { + $this->dependencies[] = $dep; + return $this; + } + + + /** + * Returns the list of dependencies. + * @return array + */ + public function getDependencies() + { + return $this->dependencies; } @@ -839,6 +738,7 @@ $entity = '@' . current(array_keys($this->definitions, $entity, TRUE)); } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...] + trigger_error("Replace object ContainerBuilder in Statement entity with '@container'.", E_USER_DEPRECATED); $entity[0] = '@' . self::THIS_CONTAINER; } return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc], Statement @@ -852,8 +752,7 @@ */ public function getServiceName($arg) { - $arg = $this->normalizeEntity($arg); - if (!is_string($arg) || !preg_match('#^@[\w\\\\.].*\z#', $arg)) { + if (!is_string($arg) || !preg_match('#^@[\w\\\\.][^:]*\z#', $arg)) { return FALSE; } $service = substr($arg, 1); @@ -861,7 +760,7 @@ $service = $this->currentService; } if (Strings::contains($service, '\\')) { - if ($this->classes === FALSE) { // may be disabled by prepareClassList + if ($this->classList === FALSE) { // may be disabled by prepareClassList return $service; } $res = $this->getByType($service); @@ -877,4 +776,63 @@ return $service; } + + /** + * Creates a list of arguments using autowiring. + * @return array + * @internal + */ + public function autowireArguments($class, $method, array $arguments) + { + $rc = new ReflectionClass($class); + if (!$rc->hasMethod($method)) { + if (!Nette\Utils\Arrays::isList($arguments)) { + throw new ServiceCreationException("Unable to pass specified arguments to $class::$method()."); + } + return $arguments; + } + + $rm = $rc->getMethod($method); + if (!$rm->isPublic()) { + throw new ServiceCreationException("$class::$method() is not callable."); + } + $this->addDependency($rm); + return Helpers::autowireArguments($rm, $arguments, $this); + } + + + /** @deprecated */ + public function generateClasses($className = 'Container', $parentName = NULL) + { + trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); + return (new PhpGenerator($this))->generate($className); + } + + + /** @deprecated */ + public function formatStatement(Statement $statement) + { + trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); + return (new PhpGenerator($this))->formatStatement($statement); + } + + + /** @deprecated */ + public function formatPhp($statement, $args) + { + array_walk_recursive($args, function (& $val) { + if ($val instanceof Statement) { + $val = $this->completeStatement($val); + + } elseif ($val === $this) { + trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED); + $val = self::literal('$this'); + + } elseif ($val instanceof ServiceDefinition) { + $val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE)); + } + }); + return (new PhpGenerator($this))->formatPhp($statement, $args); + } + } diff -Nru php-nette-2.3.10/Nette/DI/ContainerFactory.php php-nette-2.4-20160731/Nette/DI/ContainerFactory.php --- php-nette-2.3.10/Nette/DI/ContainerFactory.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/ContainerFactory.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,170 +0,0 @@ -tempDirectory = $tempDirectory; - } - - - /** - * @return Container - */ - public function create() - { - if (!class_exists($this->class)) { - $this->loadClass(); - } - return new $this->class; - } - - - /** - * @return string - */ - protected function generateCode() - { - $compiler = $this->createCompiler(); - $config = $this->generateConfig(); - $this->onCompile($this, $compiler, $config); - - $code = "configFiles as $info) { - if (is_scalar($info[0])) { - $code .= "// source: $info[0] $info[1]\n"; - } - } - $code .= "\n" . $compiler->compile($config, $this->class, $this->parentClass); - - if ($this->autoRebuild !== 'compat') { // back compatibility - $this->dependencies = array_merge($this->dependencies, $compiler->getContainerBuilder()->getDependencies()); - } - return $code; - } - - - /** - * @return array - */ - protected function generateConfig() - { - $config = array(); - $loader = $this->createLoader(); - foreach ($this->configFiles as $info) { - $info = is_scalar($info[0]) ? $loader->load($info[0], $info[1]) : $info[0]; - $config = Config\Helpers::merge($info, $config); - } - $this->dependencies = array_merge($this->dependencies, $loader->getDependencies()); - - return Config\Helpers::merge($config, $this->config); - } - - - /** - * @return void - */ - private function loadClass() - { - $key = md5(serialize(array($this->config, $this->configFiles, $this->class, $this->parentClass))); - $file = "$this->tempDirectory/$key.php"; - if (!$this->isExpired($file) && (@include $file) !== FALSE) { - return; - } - - $handle = fopen("$file.lock", 'c+'); - if (!$handle || !flock($handle, LOCK_EX)) { - throw new Nette\IOException("Unable to acquire exclusive lock on '$file.lock'."); - } - - if (!is_file($file) || $this->isExpired($file)) { - $this->dependencies = array(); - $toWrite[$file] = $this->generateCode(); - $files = $this->dependencies ? array_combine($this->dependencies, $this->dependencies) : array(); - $toWrite["$file.meta"] = serialize(@array_map('filemtime', $files)); // @ - file may not exist - - foreach ($toWrite as $name => $content) { - if (file_put_contents("$name.tmp", $content) !== strlen($content) || !rename("$name.tmp", $name)) { - @unlink("$name.tmp"); // @ - file may not exist - throw new Nette\IOException("Unable to create file '$name'."); - } - } - } - - if ((@include $file) === FALSE) { // @ - error escalated to exception - throw new Nette\IOException("Unable to include '$file'."); - } - flock($handle, LOCK_UN); - } - - - private function isExpired($file) - { - if ($this->autoRebuild) { - $meta = @unserialize(file_get_contents("$file.meta")); // @ - files may not exist - $files = $meta ? array_combine($tmp = array_keys($meta), $tmp) : array(); - return $meta !== @array_map('filemtime', $files); // @ - files may not exist - } - return FALSE; - } - - - /** - * @return Compiler - */ - protected function createCompiler() - { - return new Compiler; - } - - - /** - * @return Config\Loader - */ - protected function createLoader() - { - return new Config\Loader; - } - -} diff -Nru php-nette-2.3.10/Nette/DI/ContainerLoader.php php-nette-2.4-20160731/Nette/DI/ContainerLoader.php --- php-nette-2.3.10/Nette/DI/ContainerLoader.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/ContainerLoader.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,8 +13,10 @@ /** * DI container loader. */ -class ContainerLoader extends Nette\Object +class ContainerLoader { + use Nette\SmartObject; + /** @var bool */ private $autoRebuild = FALSE; @@ -30,12 +32,16 @@ /** - * @param mixed * @param callable function (Nette\DI\Compiler $compiler): string|NULL + * @param mixed * @return string */ - public function load($key, $generator) + public function load($generator, $key = NULL) { + if (!is_callable($generator)) { // back compatiblity + trigger_error(__METHOD__ . ': order of arguments has been swapped.', E_USER_DEPRECATED); + list($generator, $key) = [$key, $generator]; + } $class = $this->getClassName($key); if (!class_exists($class, FALSE)) { $this->loadFile($class, $generator); @@ -79,6 +85,8 @@ if (file_put_contents("$name.tmp", $content) !== strlen($content) || !rename("$name.tmp", $name)) { @unlink("$name.tmp"); // @ - file may not exist throw new Nette\IOException("Unable to create file '$name'."); + } elseif (function_exists('opcache_invalidate')) { + @opcache_invalidate($name, TRUE); // @ can be restricted } } } @@ -94,8 +102,7 @@ { if ($this->autoRebuild) { $meta = @unserialize(file_get_contents("$file.meta")); // @ - file may not exist - $files = $meta ? array_combine($tmp = array_keys($meta), $tmp) : array(); - return $meta !== @array_map('filemtime', $files); // @ - files may not exist + return empty($meta[0]) || DependencyChecker::isExpired(...$meta); } return FALSE; } @@ -107,15 +114,12 @@ protected function generate($class, $generator) { $compiler = new Compiler; - $compiler->getContainerBuilder()->setClassName($class); - $code = call_user_func_array($generator, array(& $compiler)); - $code = $code ?: implode("\n\n\n", $compiler->compile()); - $files = $compiler->getDependencies(); - $files = $files ? array_combine($files, $files) : array(); // workaround for PHP 5.3 array_combine - return array( + $compiler->setClassName($class); + $code = call_user_func_array($generator, [& $compiler]) ?: $compiler->compile(); + return [ "exportDependencies()) + ]; } } diff -Nru php-nette-2.3.10/Nette/DI/Container.php php-nette-2.4-20160731/Nette/DI/Container.php --- php-nette-2.3.10/Nette/DI/Container.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Container.php 2016-07-31 17:46:34.000000000 +0000 @@ -13,27 +13,29 @@ /** * The dependency injection container default implementation. */ -class Container extends Nette\Object +class Container { + use Nette\SmartObject; + const TAGS = 'tags'; const TYPES = 'types'; const SERVICES = 'services'; const ALIASES = 'aliases'; /** @var array user parameters */ - /*private*/public $parameters = array(); + /*private*/public $parameters = []; /** @var object[] storage for shared objects */ - private $registry = array(); + private $registry = []; /** @var array[] */ - protected $meta = array(); + protected $meta = []; /** @var array circular reference detector */ private $creating; - public function __construct(array $params = array()) + public function __construct(array $params = []) { $this->parameters = $params + $this->parameters; } @@ -136,7 +138,7 @@ $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; return isset($this->registry[$name]) || (method_exists($this, $method = self::getMethodName($name)) - && ($rm = new \ReflectionMethod($this, $method)) && $rm->getName() === $method); + && (new \ReflectionMethod($this, $method))->getName() === $method); } @@ -161,25 +163,24 @@ * @return object * @throws MissingServiceException */ - public function createService($name, array $args = array()) + public function createService($name, array $args = []) { $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; $method = self::getMethodName($name); if (isset($this->creating[$name])) { throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating)))); - } elseif (!method_exists($this, $method) || !($rm = new \ReflectionMethod($this, $method)) || $rm->getName() !== $method) { + } elseif (!method_exists($this, $method) || (new \ReflectionMethod($this, $method))->getName() !== $method) { throw new MissingServiceException("Service '$name' not found."); } - $this->creating[$name] = TRUE; try { - $service = call_user_func_array(array($this, $method), $args); - } catch (\Exception $e) { + $this->creating[$name] = TRUE; + $service = $this->$method(...$args); + + } finally { unset($this->creating[$name]); - throw $e; } - unset($this->creating[$name]); if (!is_object($service)) { throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by method $method() is not object."); @@ -220,8 +221,8 @@ { $class = ltrim($class, '\\'); return empty($this->meta[self::TYPES][$class]) - ? array() - : call_user_func_array('array_merge', $this->meta[self::TYPES][$class]); + ? [] + : array_merge(...array_values($this->meta[self::TYPES][$class])); } @@ -232,7 +233,7 @@ */ public function findByTag($tag) { - return isset($this->meta[self::TAGS][$tag]) ? $this->meta[self::TAGS][$tag] : array(); + return isset($this->meta[self::TAGS][$tag]) ? $this->meta[self::TAGS][$tag] : []; } @@ -246,7 +247,7 @@ * @return object * @throws Nette\InvalidArgumentException */ - public function createInstance($class, array $args = array()) + public function createInstance($class, array $args = []) { $rc = new \ReflectionClass($class); if (!$rc->isInstantiable()) { @@ -277,12 +278,9 @@ * Calls method using autowiring. * @return mixed */ - public function callMethod($function, array $args = array()) + public function callMethod(callable $function, array $args = []) { - return call_user_func_array( - $function, - Helpers::autowireArguments(Nette\Utils\Callback::toReflection($function), $args, $this) - ); + return $function(...Helpers::autowireArguments(Nette\Utils\Callback::toReflection($function), $args, $this)); } @@ -304,7 +302,7 @@ /** @deprecated */ public function &__get($name) { - $this->error(__METHOD__, 'getService'); + trigger_error(__METHOD__ . ' is deprecated; use getService().', E_USER_DEPRECATED); $tmp = $this->getService($name); return $tmp; } @@ -313,7 +311,7 @@ /** @deprecated */ public function __set($name, $service) { - $this->error(__METHOD__, 'addService'); + trigger_error(__METHOD__ . ' is deprecated; use addService().', E_USER_DEPRECATED); $this->addService($name, $service); } @@ -321,7 +319,7 @@ /** @deprecated */ public function __isset($name) { - $this->error(__METHOD__, 'hasService'); + trigger_error(__METHOD__ . ' is deprecated; use hasService().', E_USER_DEPRECATED); return $this->hasService($name); } @@ -329,19 +327,11 @@ /** @deprecated */ public function __unset($name) { - $this->error(__METHOD__, 'removeService'); + trigger_error(__METHOD__ . ' is deprecated; use removeService().', E_USER_DEPRECATED); $this->removeService($name); } - private function error($oldName, $newName) - { - if (empty($this->parameters['container']['accessors'])) { - trigger_error("$oldName() is deprecated; use $newName() or enable di.accessors in configuration.", E_USER_DEPRECATED); - } - } - - public static function getMethodName($name) { $uname = ucfirst($name); diff -Nru php-nette-2.3.10/Nette/DI/DependencyChecker.php php-nette-2.4-20160731/Nette/DI/DependencyChecker.php --- php-nette-2.3.10/Nette/DI/DependencyChecker.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/DependencyChecker.php 2016-07-31 17:46:34.000000000 +0000 @@ -0,0 +1,157 @@ +dependencies = array_merge($this->dependencies, $deps); + return $this; + } + + + /** + * Exports dependencies. + * @return array + */ + public function export() + { + $files = $phpFiles = $classes = $functions = []; + foreach ($this->dependencies as $dep) { + if (is_string($dep)) { + $files[] = $dep; + + } elseif ($dep instanceof ReflectionClass) { + if (empty($classes[$dep->getName()])) { + foreach (PhpReflection::getClassTree($dep) as $item) { + $phpFiles[] = (new ReflectionClass($item))->getFileName(); + $classes[$item] = TRUE; + } + } + + } elseif ($dep instanceof \ReflectionFunctionAbstract) { + $phpFiles[] = $dep->getFileName(); + $functions[] = $dep instanceof ReflectionMethod ? $dep->getDeclaringClass()->getName() . '::' . $dep->getName() : $dep->getName(); + + } else { + throw new Nette\InvalidStateException('Unexpected dependency ' . gettype($dep)); + } + } + + $classes = array_keys($classes); + $functions = array_unique($functions, SORT_REGULAR); + $hash = self::calculateHash($classes, $functions); + $files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist + $phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist + return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash]; + } + + + /** + * Are dependencies expired? + * @return bool + */ + public static function isExpired($version, $files, $phpFiles, $classes, $functions, $hash) + { + $current = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist + $currentClass = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist + return $version !== self::VERSION + || $files !== $current + || ($phpFiles !== $currentClass && $hash !== self::calculateHash($classes, $functions)); + } + + + private static function calculateHash($classes, $functions) + { + $hash = []; + foreach ($classes as $name) { + try { + $class = new ReflectionClass($name); + } catch (\ReflectionException $e) { + return; + } + $hash[] = [$name, PhpReflection::getUseStatements($class)]; + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + if ($prop->getDeclaringClass() == $class) { // intentionally == + $hash[] = [$name, $prop->getName(), $prop->getDocComment()]; + } + } + foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if ($method->getDeclaringClass() == $class) { // intentionally == + $hash[] = [ + $name, + $method->getName(), + $method->getDocComment(), + self::hashParameters($method), + PHP_VERSION >= 70000 ? $method->getReturnType() : NULL + ]; + } + } + } + + $flip = array_flip($classes); + foreach ($functions as $name) { + try { + $method = strpos($name, '::') ? new ReflectionMethod($name) : new \ReflectionFunction($name); + } catch (\ReflectionException $e) { + return; + } + $class = $method instanceof ReflectionMethod ? $method->getDeclaringClass() : NULL; + if ($class && isset($flip[$class->getName()])) { + continue; + } + $hash[] = [ + $name, + $class ? PhpReflection::getUseStatements($method->getDeclaringClass()) : NULL, + $method->getDocComment(), + self::hashParameters($method), + PHP_VERSION >= 70000 ? $method->getReturnType() : NULL + ]; + } + + return md5(serialize($hash)); + } + + + private static function hashParameters(\ReflectionFunctionAbstract $method) + { + $res = []; + foreach ($method->getParameters() as $param) { + $res[] = [ + $param->getName(), + PhpReflection::getParameterType($param), + $param->isDefaultValueAvailable() + ? ($param->isDefaultValueConstant() ? $param->getDefaultValueConstantName() : [$param->getDefaultValue()]) + : NULL + ]; + } + return $res; + } + +} diff -Nru php-nette-2.3.10/Nette/DI/Extensions/ConstantsExtension.php php-nette-2.4-20160731/Nette/DI/Extensions/ConstantsExtension.php --- php-nette-2.3.10/Nette/DI/Extensions/ConstantsExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Extensions/ConstantsExtension.php 2016-07-31 17:46:34.000000000 +0000 @@ -19,7 +19,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) { foreach ($this->getConfig() as $name => $value) { - $class->getMethod('initialize')->addBody('define(?, ?);', array($name, $value)); + $class->getMethod('initialize')->addBody('define(?, ?);', [$name, $value]); } } diff -Nru php-nette-2.3.10/Nette/DI/Extensions/DecoratorExtension.php php-nette-2.4-20160731/Nette/DI/Extensions/DecoratorExtension.php --- php-nette-2.3.10/Nette/DI/Extensions/DecoratorExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Extensions/DecoratorExtension.php 2016-07-31 17:46:34.000000000 +0000 @@ -15,11 +15,11 @@ */ class DecoratorExtension extends Nette\DI\CompilerExtension { - public $defaults = array( - 'setup' => array(), - 'tags' => array(), + public $defaults = [ + 'setup' => [], + 'tags' => [], 'inject' => NULL, - ); + ]; public function beforeCompile() @@ -29,6 +29,7 @@ if ($info['inject'] !== NULL) { $info['tags'][InjectExtension::TAG_INJECT] = $info['inject']; } + $info = Nette\DI\Helpers::filterArguments($info); $this->addSetups($class, (array) $info['setup']); $this->addTags($class, (array) $info['tags']); } @@ -58,10 +59,7 @@ { $type = ltrim($type, '\\'); return array_filter($this->getContainerBuilder()->getDefinitions(), function ($def) use ($type) { - return $def->getClass() === $type || is_subclass_of($def->getClass(), $type) - || (PHP_VERSION_ID < 50307 && array_key_exists($type, class_implements($def->getClass()))) - || $def->getImplement() === $type || is_subclass_of($def->getImplement(), $type) - || (PHP_VERSION_ID < 50307 && $def->getImplement() && array_key_exists($type, class_implements($def->getImplement()))); + return is_a($def->getClass(), $type, TRUE) || is_a($def->getImplement(), $type, TRUE); }); } diff -Nru php-nette-2.3.10/Nette/DI/Extensions/DIExtension.php php-nette-2.4-20160731/Nette/DI/Extensions/DIExtension.php --- php-nette-2.3.10/Nette/DI/Extensions/DIExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Extensions/DIExtension.php 2016-07-31 17:46:34.000000000 +0000 @@ -15,10 +15,12 @@ */ class DIExtension extends Nette\DI\CompilerExtension { - public $defaults = array( - 'debugger' => FALSE, + public $defaults = [ + 'debugger' => TRUE, 'accessors' => FALSE, - ); + 'excluded' => [], + 'parentClass' => NULL, + ]; /** @var bool */ private $debugMode; @@ -37,37 +39,29 @@ public function loadConfiguration() { $config = $this->validateConfig($this->defaults); - if ($config['accessors']) { - $this->getContainerBuilder()->parameters['container']['accessors'] = TRUE; - } + $builder = $this->getContainerBuilder(); + $builder->addExcludedClasses($config['excluded']); } public function afterCompile(Nette\PhpGenerator\ClassType $class) { + if ($this->config['parentClass']) { + $class->setExtends($this->config['parentClass']); + } + $initialize = $class->getMethod('initialize'); - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); if ($this->debugMode && $this->config['debugger']) { Nette\Bridges\DITracy\ContainerPanel::$compilationTime = $this->time; - $initialize->addBody($container->formatPhp('?;', array( - new Nette\DI\Statement('@Tracy\Bar::addPanel', array(new Nette\DI\Statement('Nette\Bridges\DITracy\ContainerPanel'))), - ))); - } - - foreach (array_filter($container->findByTag('run')) as $name => $on) { - $initialize->addBody('$this->getService(?);', array($name)); + $initialize->addBody($builder->formatPhp('?;', [ + new Nette\DI\Statement('@Tracy\Bar::addPanel', [new Nette\DI\Statement(Nette\Bridges\DITracy\ContainerPanel::class)]), + ])); } - if (!empty($this->config['accessors'])) { - $definitions = $container->getDefinitions(); - ksort($definitions); - foreach ($definitions as $name => $def) { - if (Nette\PhpGenerator\Helpers::isIdentifier($name)) { - $type = $def->getImplement() ?: $def->getClass(); - $class->addDocument("@property $type \$$name"); - } - } + foreach (array_filter($builder->findByTag('run')) as $name => $on) { + $initialize->addBody('$this->getService(?);', [$name]); } } diff -Nru php-nette-2.3.10/Nette/DI/Extensions/InjectExtension.php php-nette-2.4-20160731/Nette/DI/Extensions/InjectExtension.php --- php-nette-2.3.10/Nette/DI/Extensions/InjectExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Extensions/InjectExtension.php 2016-07-31 17:46:34.000000000 +0000 @@ -34,10 +34,10 @@ { $class = $def->getClass(); $builder = $this->getContainerBuilder(); - $injects = array(); + $injects = []; foreach (self::getInjectProperties($class) as $property => $type) { self::checkType($class, $property, $type, $builder); - $injects[] = new DI\Statement('$' . $property, array('@\\' . ltrim($type, '\\'))); + $injects[] = new DI\Statement('$' . $property, ['@\\' . ltrim($type, '\\')]); } foreach (self::getInjectMethods($def->getClass()) as $method) { @@ -78,7 +78,7 @@ */ public static function getInjectProperties($class) { - $res = array(); + $res = []; foreach (get_class_vars($class) as $name => $foo) { $rp = new \ReflectionProperty($class, $name); if (PhpReflection::parseAnnotation($rp, 'inject') !== NULL) { @@ -88,6 +88,7 @@ $res[$name] = $type; } } + ksort($res); return $res; } @@ -103,7 +104,7 @@ } foreach (array_reverse(self::getInjectMethods($service)) as $method) { - $container->callMethod(array($service, $method)); + $container->callMethod([$service, $method]); } foreach (self::getInjectProperties(get_class($service)) as $property => $type) { diff -Nru php-nette-2.3.10/Nette/DI/Extensions/PhpExtension.php php-nette-2.4-20160731/Nette/DI/Extensions/PhpExtension.php --- php-nette-2.3.10/Nette/DI/Extensions/PhpExtension.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Extensions/PhpExtension.php 2016-07-31 17:46:34.000000000 +0000 @@ -27,19 +27,19 @@ throw new Nette\InvalidStateException("Configuration value for directive '$name' is not scalar."); } elseif ($name === 'include_path') { - $initialize->addBody('set_include_path(?);', array(str_replace(';', PATH_SEPARATOR, $value))); + $initialize->addBody('set_include_path(?);', [str_replace(';', PATH_SEPARATOR, $value)]); } elseif ($name === 'ignore_user_abort') { - $initialize->addBody('ignore_user_abort(?);', array($value)); + $initialize->addBody('ignore_user_abort(?);', [$value]); } elseif ($name === 'max_execution_time') { - $initialize->addBody('set_time_limit(?);', array($value)); + $initialize->addBody('set_time_limit(?);', [$value]); } elseif ($name === 'date.timezone') { - $initialize->addBody('date_default_timezone_set(?);', array($value)); + $initialize->addBody('date_default_timezone_set(?);', [$value]); } elseif (function_exists('ini_set')) { - $initialize->addBody('ini_set(?, ?);', array($name, $value)); + $initialize->addBody('ini_set(?, ?);', [$name, $value]); } elseif (ini_get($name) != $value) { // intentionally == throw new Nette\NotSupportedException('Required function ini_set() is disabled.'); diff -Nru php-nette-2.3.10/Nette/DI/Helpers.php php-nette-2.4-20160731/Nette/DI/Helpers.php --- php-nette-2.3.10/Nette/DI/Helpers.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Helpers.php 2016-07-31 17:46:34.000000000 +0000 @@ -16,6 +16,7 @@ */ class Helpers { + use Nette\StaticClass; /** * Expands %placeholders%. @@ -28,7 +29,7 @@ public static function expand($var, array $params, $recursive = FALSE) { if (is_array($var)) { - $res = array(); + $res = []; foreach ($var as $key => $val) { $res[$key] = self::expand($val, $params, $recursive); } @@ -60,7 +61,7 @@ throw new Nette\InvalidArgumentException("Missing parameter '$part'.", 0, $e); } if ($recursive) { - $val = self::expand($val, $params, (is_array($recursive) ? $recursive : array()) + array($part => 1)); + $val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]); } if (strlen($part) + 2 === strlen($var)) { return $val; @@ -83,19 +84,19 @@ { $optCount = 0; $num = -1; - $res = array(); + $res = []; $methodName = ($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName() . '()'; foreach ($method->getParameters() as $num => $parameter) { - if (array_key_exists($num, $arguments)) { - $res[$num] = $arguments[$num]; - unset($arguments[$num]); + if (!$parameter->isVariadic() && array_key_exists($parameter->getName(), $arguments)) { + $res[$num] = $arguments[$parameter->getName()]; + unset($arguments[$parameter->getName()], $arguments[$num]); $optCount = 0; - } elseif (array_key_exists($parameter->getName(), $arguments)) { - $res[$num] = $arguments[$parameter->getName()]; - unset($arguments[$parameter->getName()]); + } elseif (array_key_exists($num, $arguments)) { + $res[$num] = $arguments[$num]; + unset($arguments[$num]); $optCount = 0; } elseif (($class = PhpReflection::getParameterType($parameter)) && !PhpReflection::isBuiltinType($class)) { @@ -104,8 +105,7 @@ if ($parameter->allowsNull()) { $optCount++; } elseif (class_exists($class) || interface_exists($class)) { - $rc = new \ReflectionClass($class); - if ($class !== ($hint = $rc->getName())) { + if ($class !== ($hint = (new \ReflectionClass($class))->getName())) { throw new ServiceCreationException("Service of type {$class} needed by $methodName not found, did you mean $hint?"); } throw new ServiceCreationException("Service of type {$class} needed by $methodName not found. Did you register it in configuration file?"); @@ -120,7 +120,7 @@ } } elseif ($parameter->isOptional() || $parameter->isDefaultValueAvailable()) { - // !optional + defaultAvailable = func($a = NULL, $b) since 5.3.17 & 5.4.7 + // !optional + defaultAvailable = func($a = NULL, $b) since 5.4.7 // optional + !defaultAvailable = i.e. Exception::__construct, mysqli::mysqli, ... $res[$num] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : NULL; $optCount++; @@ -143,4 +143,52 @@ return $optCount ? array_slice($res, 0, -$optCount) : $res; } + + /** + * Removes ... and process constants recursively. + * @return array + */ + public static function filterArguments(array $args) + { + foreach ($args as $k => $v) { + if ($v === '...') { + unset($args[$k]); + } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) { + $args[$k] = constant(ltrim($v, ':')); + } elseif (is_array($v)) { + $args[$k] = self::filterArguments($v); + } elseif ($v instanceof Statement) { + $tmp = self::filterArguments([$v->getEntity()]); + $args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments)); + } + } + return $args; + } + + + /** + * Replaces @extension with real extension name in service definition. + * @param mixed + * @param string + * @return mixed + */ + public static function prefixServiceName($config, $namespace) + { + if (is_string($config)) { + if (strncmp($config, '@extension.', 10) === 0) { + $config = '@' . $namespace . '.' . substr($config, 11); + } + } elseif ($config instanceof Statement) { + return new Statement( + self::prefixServiceName($config->getEntity(), $namespace), + self::prefixServiceName($config->arguments, $namespace) + ); + } elseif (is_array($config)) { + foreach ($config as & $val) { + $val = self::prefixServiceName($val, $namespace); + } + } + return $config; + } + } diff -Nru php-nette-2.3.10/Nette/DI/PhpGenerator.php php-nette-2.4-20160731/Nette/DI/PhpGenerator.php --- php-nette-2.3.10/Nette/DI/PhpGenerator.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/PhpGenerator.php 2016-07-31 17:46:34.000000000 +0000 @@ -0,0 +1,264 @@ +builder = $builder; + } + + + /** + * Generates PHP classes. First class is the container. + * @return Nette\PhpGenerator\ClassType[] + */ + public function generate($className) + { + $this->builder->complete(); + + $this->generatedClasses = []; + $this->className = $className; + $containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className); + $containerClass->setExtends(Container::class); + $containerClass->addMethod('__construct') + ->addBody('parent::__construct(?);', [$this->builder->parameters]); + + $definitions = $this->builder->getDefinitions(); + ksort($definitions); + + $meta = $containerClass->addProperty('meta') + ->setVisibility('protected') + ->setValue([Container::TYPES => $this->builder->getClassList()]); + + foreach ($definitions as $name => $def) { + $meta->value[Container::SERVICES][$name] = $def->getClass() ?: NULL; + foreach ($def->getTags() as $tag => $value) { + $meta->value[Container::TAGS][$tag][$name] = $value; + } + } + + foreach ($definitions as $name => $def) { + try { + $name = (string) $name; + $methodName = Container::getMethodName($name); + if (!PhpHelpers::isIdentifier($methodName)) { + throw new ServiceCreationException('Name contains invalid characters.'); + } + $containerClass->addMethod($methodName) + ->addComment('@return ' . ($def->getImplement() ?: $def->getClass())) + ->setBody($name === ContainerBuilder::THIS_CONTAINER ? 'return $this;' : $this->generateService($name)) + ->setParameters($def->getImplement() ? [] : $this->convertParameters($def->parameters)); + } catch (\Exception $e) { + throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); + } + } + + $aliases = $this->builder->getAliases(); + ksort($aliases); + $meta->value[Container::ALIASES] = $aliases; + + return $this->generatedClasses; + } + + + /** + * Generates body of service method. + * @return string + */ + private function generateService($name) + { + $def = $this->builder->getDefinition($name); + + if ($def->isDynamic()) { + return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);', + ["Unable to create dynamic service '$name', it must be added using addService()"] + ); + } + + $entity = $def->getFactory()->getEntity(); + $serviceRef = $this->builder->getServiceName($entity); + $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE + ? new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$serviceRef]) + : $def->getFactory(); + + $this->currentService = NULL; + $code = '$service = ' . $this->formatStatement($factory) . ";\n"; + + if (($class = $def->getClass()) && !$serviceRef && $class !== $entity + && !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $class)) + ) { + $code .= PhpHelpers::formatArgs("if (!\$service instanceof $class) {\n" + . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n", + ["Unable to create service '$name', value returned by factory is not $class type."] + ); + } + + $this->currentService = $name; + foreach ($def->getSetup() as $setup) { + $code .= $this->formatStatement($setup) . ";\n"; + } + + $code .= 'return $service;'; + + if (!$def->getImplement()) { + return $code; + } + + $factoryClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType; + $factoryClass->setName(str_replace(['\\', '.'], '_', "{$this->className}_{$def->getImplement()}Impl_{$name}")) + ->addImplement($def->getImplement()) + ->setFinal(TRUE); + + $factoryClass->addProperty('container') + ->setVisibility('private'); + + $factoryClass->addMethod('__construct') + ->addBody('$this->container = $container;') + ->addParameter('container') + ->setTypeHint($this->className); + + $factoryClass->addMethod($def->getImplementMode()) + ->setParameters($this->convertParameters($def->parameters)) + ->setBody(str_replace('$this', '$this->container', $code)) + ->setReturnType(PHP_VERSION_ID >= 70000 ? $def->getClass() : NULL); + + return "return new {$factoryClass->getName()}(\$this);"; + } + + + /** + * Formats PHP code for class instantiating, function calling or property setting in PHP. + * @return string + */ + private function formatStatement(Statement $statement) + { + $entity = $statement->getEntity(); + $arguments = $statement->arguments; + + if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal + return $this->formatPhp($entity, $arguments); + + } elseif ($service = $this->builder->getServiceName($entity)) { // factory calling + return $this->formatPhp('$this->?(?*)', [Container::getMethodName($service), $arguments]); + + } elseif ($entity === 'not') { // operator + return $this->formatPhp('!?', [$arguments[0]]); + + } elseif (is_string($entity)) { // class name + return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), [$arguments]); + + } elseif ($entity[0] === '') { // globalFunc + return $this->formatPhp("$entity[1](?*)", [$arguments]); + + } elseif ($entity[0] instanceof Statement) { + $inner = $this->formatPhp('?', [$entity[0]]); + if (substr($inner, 0, 4) === 'new ') { + $inner = "($inner)"; + } + return $this->formatPhp("$inner->?(?*)", [$entity[1], $arguments]); + + } elseif ($entity[1][0] === '$') { // property getter, setter or appender + $name = substr($entity[1], 1); + if ($append = (substr($name, -2) === '[]')) { + $name = substr($name, 0, -2); + } + if ($this->builder->getServiceName($entity[0])) { + $prop = $this->formatPhp('?->?', [$entity[0], $name]); + } else { + $prop = $this->formatPhp($entity[0] . '::$?', [$name]); + } + return $arguments + ? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]]) + : $prop; + + } elseif ($service = $this->builder->getServiceName($entity[0])) { // service method + return $this->formatPhp('?->?(?*)', [$entity[0], $entity[1], $arguments]); + + } else { // static method + return $this->formatPhp("$entity[0]::$entity[1](?*)", [$arguments]); + } + } + + + /** + * Formats PHP statement. + * @return string + * @internal + */ + public function formatPhp($statement, $args) + { + array_walk_recursive($args, function (& $val) { + if ($val instanceof Statement) { + $val = new PhpLiteral($this->formatStatement($val)); + + } elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@ + $val = substr($val, 1); + + } elseif (is_string($val) && substr($val, 0, 1) === '@' && strlen($val) > 1) { // service reference + $name = substr($val, 1); + if ($name === ContainerBuilder::THIS_CONTAINER) { + $val = new PhpLiteral('$this'); + } elseif ($name === $this->currentService) { + $val = new PhpLiteral('$service'); + } else { + $val = new PhpLiteral($this->formatStatement(new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$name]))); + } + } + }); + return PhpHelpers::formatArgs($statement, $args); + } + + + /** + * Converts parameters from ServiceDefinition to PhpGenerator. + * @return Nette\PhpGenerator\Parameter[] + */ + private function convertParameters(array $parameters) + { + $res = []; + foreach ($parameters as $k => $v) { + $tmp = explode(' ', is_int($k) ? $v : $k); + $param = $res[] = new Nette\PhpGenerator\Parameter; + $param->setName(end($tmp)); + if (!is_int($k)) { + $param = $param->setOptional(TRUE)->setDefaultValue($v); + } + if (isset($tmp[1])) { + $param->setTypeHint($tmp[0]); + } + } + return $res; + } + +} diff -Nru php-nette-2.3.10/Nette/DI/PhpReflection.php php-nette-2.4-20160731/Nette/DI/PhpReflection.php --- php-nette-2.3.10/Nette/DI/PhpReflection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/PhpReflection.php 2016-07-31 17:46:34.000000000 +0000 @@ -16,9 +16,7 @@ */ class PhpReflection { - /** @var array for expandClassName() */ - private static $cache; - + use Nette\StaticClass; /** * Returns an annotation value. @@ -28,13 +26,13 @@ { static $ok; if (!$ok) { - $rc = new \ReflectionMethod(__METHOD__); - if (!$rc->getDocComment()) { + if (!(new \ReflectionMethod(__METHOD__))->getDocComment()) { throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); } $ok = TRUE; } - if (preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) { + $name = preg_quote($name, '#'); + if ($ref->getDocComment() && preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) { return isset($m[1]) ? $m[1] : ''; } } @@ -46,11 +44,9 @@ */ public static function getDeclaringClass(\ReflectionProperty $prop) { - if (PHP_VERSION_ID >= 50400) { - foreach ($prop->getDeclaringClass()->getTraits() as $trait) { - if ($trait->hasProperty($prop->getName())) { - return self::getDeclaringClass($trait->getProperty($prop->getName())); - } + foreach ($prop->getDeclaringClass()->getTraits() as $trait) { + if ($trait->hasProperty($prop->getName())) { + return self::getDeclaringClass($trait->getProperty($prop->getName())); } } return $prop->getDeclaringClass(); @@ -64,10 +60,8 @@ { if (PHP_VERSION_ID >= 70000) { return $param->hasType() ? (string) $param->getType() : NULL; - } elseif ($param->isArray()) { - return 'array'; - } elseif (PHP_VERSION_ID >= 50400 && $param->isCallable()) { - return 'callable'; + } elseif ($param->isArray() || $param->isCallable()) { + return $param->isArray() ? 'array' : 'callable'; } else { try { return ($ref = $param->getClass()) ? $ref->getName() : NULL; @@ -87,7 +81,8 @@ public static function getReturnType(\ReflectionFunctionAbstract $func) { if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) { - return (string) $func->getReturnType(); + $type = (string) $func->getReturnType(); + return strtolower($type) === 'self' ? $func->getDeclaringClass()->getName() : $type; } $type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return')); if ($type) { @@ -104,7 +99,24 @@ */ public static function isBuiltinType($type) { - return in_array(strtolower($type), array('string', 'int', 'float', 'bool', 'array', 'callable'), TRUE); + return in_array(strtolower($type), ['string', 'int', 'float', 'bool', 'array', 'callable'], TRUE); + } + + + /** + * Returns class and all its descendants. + * @return string[] + */ + public static function getClassTree(\ReflectionClass $class) + { + $addTraits = function ($types) use (& $addTraits) { + if ($traits = array_merge(...array_map('class_uses', array_values($types)))) { + $types += $traits + $addTraits($traits); + } + return $types; + }; + $class = $class->getName(); + return array_values($addTraits([$class] + class_parents($class) + class_implements($class))); } @@ -130,11 +142,7 @@ return ltrim($name, '\\'); } - $uses = & self::$cache[$rc->getName()]; - if ($uses === NULL) { - self::$cache = self::parseUseStatemenets(file_get_contents($rc->getFileName()), $rc->getName()) + self::$cache; - $uses = & self::$cache[$rc->getName()]; - } + $uses = self::getUseStatements($rc); $parts = explode('\\', $name, 2); if (isset($uses[$parts[0]])) { $parts[0] = $uses[$parts[0]]; @@ -150,26 +158,44 @@ /** + * @return array of [alias => class] + */ + public static function getUseStatements(\ReflectionClass $class) + { + static $cache = []; + if (!isset($cache[$name = $class->getName()])) { + if ($class->isInternal()) { + $cache[$name] = []; + } else { + $code = file_get_contents($class->getFileName()); + $cache = self::parseUseStatements($code, $name) + $cache; + } + } + return $cache[$name]; + } + + + /** * Parses PHP code. * @param string - * @return array + * @return array of [class => [alias => class, ...]] */ - public static function parseUseStatemenets($code, $forClass = NULL) + public static function parseUseStatements($code, $forClass = NULL) { $tokens = token_get_all($code); $namespace = $class = $classLevel = $level = NULL; - $res = $uses = array(); + $res = $uses = []; while (list(, $token) = each($tokens)) { switch (is_array($token) ? $token[0] : $token) { case T_NAMESPACE: - $namespace = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)) . '\\'; - $uses = array(); + $namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\'); + $uses = []; break; case T_CLASS: case T_INTERFACE: - case PHP_VERSION_ID < 50400 ? -1 : T_TRAIT: + case T_TRAIT: if ($name = self::fetch($tokens, T_STRING)) { $class = $namespace . $name; $classLevel = $level + 1; @@ -181,7 +207,7 @@ break; case T_USE: - while (!$class && ($name = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)))) { + while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) { $name = ltrim($name, '\\'); if (self::fetch($tokens, T_AS)) { $uses[self::fetch($tokens, T_STRING)] = $name; @@ -217,10 +243,10 @@ { $res = NULL; while ($token = current($tokens)) { - list($token, $s) = is_array($token) ? $token : array($token, $token); + list($token, $s) = is_array($token) ? $token : [$token, $token]; if (in_array($token, (array) $take, TRUE)) { $res .= $s; - } elseif (!in_array($token, array(T_DOC_COMMENT, T_WHITESPACE, T_COMMENT), TRUE)) { + } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], TRUE)) { break; } next($tokens); diff -Nru php-nette-2.3.10/Nette/DI/ServiceDefinition.php php-nette-2.4-20160731/Nette/DI/ServiceDefinition.php --- php-nette-2.3.10/Nette/DI/ServiceDefinition.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/ServiceDefinition.php 2016-07-31 17:46:34.000000000 +0000 @@ -12,9 +12,19 @@ /** * Definition used by ContainerBuilder. + * + * @property string|NULL $class + * @property Statement|NULL $factory + * @property Statement[] $setup */ -class ServiceDefinition extends Nette\Object +class ServiceDefinition { + const + IMPLEMENT_MODE_CREATE = 'create', + IMPLEMENT_MODE_GET = 'get'; + + use Nette\SmartObject; + /** @var string|NULL class or interface name */ private $class; @@ -22,15 +32,15 @@ private $factory; /** @var Statement[] */ - private $setup = array(); + private $setup = []; /** @var array */ - public $parameters = array(); + public $parameters = []; /** @var array */ - private $tags = array(); + private $tags = []; - /** @var bool */ + /** @var bool|string[] */ private $autowired = TRUE; /** @var bool */ @@ -40,15 +50,19 @@ private $implement; /** @var string|NULL create | get */ - private $implementType; + private $implementMode; + + /** @var callable */ + private $notifier = 'pi'; // = noop /** * @return self */ - public function setClass($class, array $args = array()) + public function setClass($class, array $args = []) { - $this->class = ltrim($class, '\\'); + call_user_func($this->notifier); + $this->class = $class ? ltrim($class, '\\') : NULL; if ($args) { $this->setFactory($class, $args); } @@ -68,8 +82,9 @@ /** * @return self */ - public function setFactory($factory, array $args = array()) + public function setFactory($factory, array $args = []) { + call_user_func($this->notifier); $this->factory = $factory instanceof Statement ? $factory : new Statement($factory, $args); return $this; } @@ -96,7 +111,7 @@ /** * @return self */ - public function setArguments(array $args = array()) + public function setArguments(array $args = []) { if (!$this->factory) { $this->factory = new Statement($this->class); @@ -134,7 +149,7 @@ /** * @return self */ - public function addSetup($entity, array $args = array()) + public function addSetup($entity, array $args = []) { $this->setup[] = $entity instanceof Statement ? $entity : new Statement($entity, $args); return $this; @@ -199,18 +214,19 @@ /** - * @param bool + * @param bool|string|string[] * @return self */ public function setAutowired($state = TRUE) { - $this->autowired = (bool) $state; + call_user_func($this->notifier); + $this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state; return $this; } /** - * @return bool + * @return bool|string[] */ public function isAutowired() { @@ -219,6 +235,15 @@ /** + * @return bool|string[] + */ + public function getAutowired() + { + return $this->autowired; + } + + + /** * @param bool * @return self */ @@ -244,6 +269,7 @@ */ public function setImplement($interface) { + call_user_func($this->notifier); $this->implement = ltrim($interface, '\\'); return $this; } @@ -262,12 +288,12 @@ * @param string * @return self */ - public function setImplementType($type) + public function setImplementMode($mode) { - if (!in_array($type, array('get', 'create'), TRUE)) { + if (!in_array($mode, [self::IMPLEMENT_MODE_CREATE, self::IMPLEMENT_MODE_GET], TRUE)) { throw new Nette\InvalidArgumentException('Argument must be get|create.'); } - $this->implementType = $type; + $this->implementMode = $mode; return $this; } @@ -275,25 +301,25 @@ /** * @return string|NULL */ - public function getImplementType() + public function getImplementMode() { - return $this->implementType; + return $this->implementMode; } /** @deprecated */ - public function setShared($on) + public function setImplementType($type) { - trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); - $this->autowired = $on ? $this->autowired : FALSE; - return $this; + trigger_error(__METHOD__ . '() is deprecated, use setImplementMode()', E_USER_DEPRECATED); + return $this->setImplementMode($type); } /** @deprecated */ - public function isShared() + public function getImplementType() { - trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); + trigger_error(__METHOD__ . '() is deprecated, use getImplementMode()', E_USER_DEPRECATED); + return $this->implementMode; } @@ -312,4 +338,21 @@ return $this->getTag(Extensions\InjectExtension::TAG_INJECT); } + + /** + * @internal + */ + public function setNotifier(callable $notifier) + { + $this->notifier = $notifier; + } + + + public function __clone() + { + $this->factory = unserialize(serialize($this->factory)); + $this->setup = unserialize(serialize($this->setup)); + $this->notifier = 'pi'; + } + } diff -Nru php-nette-2.3.10/Nette/DI/Statement.php php-nette-2.4-20160731/Nette/DI/Statement.php --- php-nette-2.3.10/Nette/DI/Statement.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/DI/Statement.php 2016-07-31 17:46:34.000000000 +0000 @@ -12,9 +12,13 @@ /** * Assignment or calling statement. + * + * @property string|array|ServiceDefinition|NULL $entity */ -class Statement extends Nette\Object +class Statement { + use Nette\SmartObject; + /** @var string|array|ServiceDefinition|NULL class|method|$property */ private $entity; @@ -25,18 +29,7 @@ /** * @param string|array|ServiceDefinition|NULL */ - public function __construct($entity, array $arguments = array()) - { - $this->setEntity($entity); - $this->arguments = $arguments; - } - - - /** - * @param string|array|ServiceDefinition|NULL - * @return self - */ - public function setEntity($entity) + public function __construct($entity, array $arguments = []) { if (!is_string($entity) && !(is_array($entity) && isset($entity[0], $entity[1])) && !$entity instanceof ServiceDefinition && $entity !== NULL @@ -44,6 +37,15 @@ throw new Nette\InvalidArgumentException('Argument is not valid Statement entity.'); } $this->entity = $entity; + $this->arguments = $arguments; + } + + + /** @deprecated */ + public function setEntity($entity) + { + trigger_error(__METHOD__ . ' is deprecated, change Statement object itself.', E_USER_DEPRECATED); + $this->__construct($entity, $this->arguments); return $this; } diff -Nru php-nette-2.3.10/Nette/Finder/CallbackFilterIterator.php php-nette-2.4-20160731/Nette/Finder/CallbackFilterIterator.php --- php-nette-2.3.10/Nette/Finder/CallbackFilterIterator.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Finder/CallbackFilterIterator.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -callback = $callback; - } - - - public function accept() - { - return call_user_func($this->callback, $this->current(), $this->key(), $this); - } - -} diff -Nru php-nette-2.3.10/Nette/Finder/Finder.php php-nette-2.4-20160731/Nette/Finder/Finder.php --- php-nette-2.3.10/Nette/Finder/Finder.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Finder/Finder.php 2016-07-31 17:46:36.000000000 +0000 @@ -22,16 +22,18 @@ * ->exclude('temp'); * */ -class Finder extends Nette\Object implements \IteratorAggregate, \Countable +class Finder implements \IteratorAggregate, \Countable { + use Nette\SmartObject; + /** @var array */ - private $paths = array(); + private $paths = []; /** @var array of filters */ private $groups; /** @var array filter for recursive traversing */ - private $exclude = array(); + private $exclude = []; /** @var int */ private $order = RecursiveIteratorIterator::SELF_FIRST; @@ -46,45 +48,34 @@ /** * Begins search for files matching mask and all directories. * @param mixed - * @return Finder + * @return self */ - public static function find($mask) + public static function find(...$masks) { - if (!is_array($mask)) { - $mask = func_get_args(); - } - $finder = new static; - return $finder->select($mask, 'isDir')->select($mask, 'isFile'); + $masks = is_array($masks[0]) ? $masks[0] : $masks; + return (new static)->select($masks, 'isDir')->select($masks, 'isFile'); } /** * Begins search for files matching mask. * @param mixed - * @return Finder + * @return self */ - public static function findFiles($mask) + public static function findFiles(...$masks) { - if (!is_array($mask)) { - $mask = func_get_args(); - } - $finder = new static; - return $finder->select($mask, 'isFile'); + return (new static)->select(is_array($masks[0]) ? $masks[0] : $masks, 'isFile'); } /** * Begins search for directories matching mask. * @param mixed - * @return Finder + * @return self */ - public static function findDirectories($mask) + public static function findDirectories(...$masks) { - if (!is_array($mask)) { - $mask = func_get_args(); - } - $finder = new static; - return $finder->select($mask, 'isDir'); + return (new static)->select(is_array($masks[0]) ? $masks[0] : $masks, 'isDir'); } @@ -114,13 +105,10 @@ * @param string|array * @return self */ - public function in($path) + public function in(...$paths) { - if (!is_array($path)) { - $path = func_get_args(); - } $this->maxDepth = 0; - return $this->from($path); + return $this->from(...$paths); } @@ -129,15 +117,12 @@ * @param string|array * @return self */ - public function from($path) + public function from(...$paths) { if ($this->paths) { throw new Nette\InvalidStateException('Directory to search has already been specified.'); } - if (!is_array($path)) { - $path = func_get_args(); - } - $this->paths = $path; + $this->paths = is_array($paths[0]) ? $paths[0] : $paths; $this->cursor = & $this->exclude; return $this; } @@ -161,7 +146,7 @@ */ private static function buildPattern($masks) { - $pattern = array(); + $pattern = []; foreach ($masks as $mask) { $mask = rtrim(strtr($mask, '\\', '/'), '/'); $prefix = ''; @@ -176,7 +161,7 @@ $prefix = '(?<=^/)'; } $pattern[] = $prefix . strtr(preg_quote($mask, '#'), - array('\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-')); + ['\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-']); } return $pattern ? '#/(' . implode('|', $pattern) . ')\z#i' : NULL; } @@ -209,7 +194,7 @@ } else { $iterator = new \AppendIterator(); - $iterator->append($workaround = new \ArrayIterator(array('workaround PHP bugs #49104, #63077'))); + $iterator->append($workaround = new \ArrayIterator(['workaround PHP bugs #49104, #63077'])); foreach ($this->paths as $path) { $iterator->append($this->buildIterator($path)); } @@ -229,11 +214,9 @@ $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS); if ($this->exclude) { - $filters = $this->exclude; - $iterator = new RecursiveCallbackFilterIterator($iterator, function ($foo, $bar, RecursiveCallbackFilterIterator $iterator) use ($filters) { - $file = $iterator->getInnerIterator(); + $iterator = new \RecursiveCallbackFilterIterator($iterator, function ($foo, $bar, RecursiveDirectoryIterator $file) { if (!$file->isDot() && !$file->isFile()) { - foreach ($filters as $filter) { + foreach ($this->exclude as $filter) { if (!call_user_func($filter, $file)) { return FALSE; } @@ -248,13 +231,12 @@ $iterator->setMaxDepth($this->maxDepth); } - $groups = $this->groups; - $iterator = new CallbackFilterIterator($iterator, function ($foo, $bar, CallbackFilterIterator $file) use ($groups) { - do { + $iterator = new \CallbackFilterIterator($iterator, function ($foo, $bar, \Iterator $file) { + while ($file instanceof \OuterIterator) { $file = $file->getInnerIterator(); - } while (!$file instanceof RecursiveDirectoryIterator); + } - foreach ($groups as $filters) { + foreach ($this->groups as $filters) { foreach ($filters as $filter) { if (!call_user_func($filter, $file)) { continue 2; @@ -278,12 +260,9 @@ * @param mixed * @return self */ - public function exclude($masks) + public function exclude(...$masks) { - if (!is_array($masks)) { - $masks = func_get_args(); - } - $pattern = self::buildPattern($masks); + $pattern = self::buildPattern(is_array($masks[0]) ? $masks[0] : $masks); if ($pattern) { $this->filter(function (RecursiveDirectoryIterator $file) use ($pattern) { return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/')); @@ -330,12 +309,12 @@ throw new Nette\InvalidArgumentException('Invalid size predicate format.'); } list(, $operator, $size, $unit) = $matches; - static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9); + static $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9]; $size *= $units[strtolower($unit)]; $operator = $operator ? $operator : '='; } return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $size) { - return Finder::compare($file->getSize(), $operator, $size); + return self::compare($file->getSize(), $operator, $size); }); } @@ -357,7 +336,7 @@ } $date = DateTime::from($date)->format('U'); return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $date) { - return Finder::compare($file->getMTime(), $operator, $date); + return self::compare($file->getMTime(), $operator, $date); }); } @@ -391,4 +370,22 @@ } } + + /********************* extension methods ****************d*g**/ + + + public function __call($name, $args) + { + if ($callback = Nette\Utils\ObjectMixin::getExtensionMethod(__CLASS__, $name)) { + return $callback($this, ...$args); + } + Nette\Utils\ObjectMixin::strictCall(__CLASS__, $name); + } + + + public static function extensionMethod($name, $callback) + { + Nette\Utils\ObjectMixin::setExtensionMethod(__CLASS__, $name, $callback); + } + } diff -Nru php-nette-2.3.10/Nette/Finder/RecursiveCallbackFilterIterator.php php-nette-2.4-20160731/Nette/Finder/RecursiveCallbackFilterIterator.php --- php-nette-2.3.10/Nette/Finder/RecursiveCallbackFilterIterator.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Finder/RecursiveCallbackFilterIterator.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -getInnerIterator()->hasChildren(); - } - - - public function getChildren() - { - return new static($this->getInnerIterator()->getChildren(), $this->callback); - } - -} diff -Nru php-nette-2.3.10/Nette/Forms/Container.php php-nette-2.4-20160731/Nette/Forms/Container.php --- php-nette-2.3.10/Nette/Forms/Container.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Container.php 2016-07-31 17:46:36.000000000 +0000 @@ -53,6 +53,7 @@ * @param array|\Traversable values used to fill the form * @param bool erase other controls? * @return self + * @internal */ public function setValues($values, $erase = FALSE) { @@ -77,7 +78,7 @@ $control->setValues($values[$name], $erase); } elseif ($erase) { - $control->setValues(array(), $erase); + $control->setValues([], $erase); } } } @@ -92,7 +93,7 @@ */ public function getValues($asArray = FALSE) { - $values = $asArray ? array() : new Nette\Utils\ArrayHash; + $values = $asArray ? [] : new Nette\Utils\ArrayHash; foreach ($this->getComponents() as $name => $control) { if ($control instanceof IControl && !$control->isOmitted()) { $values[$name] = $control->getValue(); @@ -132,7 +133,9 @@ public function validate(array $controls = NULL) { foreach ($controls === NULL ? $this->getComponents() : $controls as $control) { - $control->validate(); + if ($control instanceof IControl || $control instanceof self) { + $control->validate(); + } } if ($this->onValidate !== NULL) { if (!is_array($this->onValidate) && !$this->onValidate instanceof \Traversable) { @@ -154,7 +157,7 @@ */ public function getErrors() { - $errors = array(); + $errors = []; foreach ($this->getControls() as $control) { $errors = array_merge($errors, $control->getErrors()); } @@ -196,7 +199,7 @@ public function addComponent(Nette\ComponentModel\IComponent $component, $name, $insertBefore = NULL) { parent::addComponent($component, $name, $insertBefore); - if ($this->currentGroup !== NULL && $component instanceof IControl) { + if ($this->currentGroup !== NULL) { $this->currentGroup->add($component); } return $this; @@ -209,7 +212,7 @@ */ public function getControls() { - return $this->getComponents(TRUE, 'Nette\Forms\IControl'); + return $this->getComponents(TRUE, IControl::class); } @@ -220,7 +223,7 @@ */ public function getForm($need = TRUE) { - return $this->lookup('Nette\Forms\Form', $need); + return $this->lookup(Form::class, $need); } @@ -233,13 +236,12 @@ * @param string label * @param int width of the control (deprecated) * @param int maximum number of characters the user may enter - * @return Nette\Forms\Controls\TextInput + * @return Controls\TextInput */ public function addText($name, $label = NULL, $cols = NULL, $maxLength = NULL) { - $control = new Controls\TextInput($label, $maxLength); - $control->setAttribute('size', $cols); - return $this[$name] = $control; + return $this[$name] = (new Controls\TextInput($label, $maxLength)) + ->setAttribute('size', $cols); } @@ -249,13 +251,13 @@ * @param string label * @param int width of the control (deprecated) * @param int maximum number of characters the user may enter - * @return Nette\Forms\Controls\TextInput + * @return Controls\TextInput */ public function addPassword($name, $label = NULL, $cols = NULL, $maxLength = NULL) { - $control = new Controls\TextInput($label, $maxLength); - $control->setAttribute('size', $cols); - return $this[$name] = $control->setType('password'); + return $this[$name] = (new Controls\TextInput($label, $maxLength)) + ->setAttribute('size', $cols) + ->setType('password'); } @@ -265,13 +267,41 @@ * @param string label * @param int width of the control * @param int height of the control in text lines - * @return Nette\Forms\Controls\TextArea + * @return Controls\TextArea */ public function addTextArea($name, $label = NULL, $cols = NULL, $rows = NULL) { - $control = new Controls\TextArea($label); - $control->setAttribute('cols', $cols)->setAttribute('rows', $rows); - return $this[$name] = $control; + return $this[$name] = (new Controls\TextArea($label)) + ->setAttribute('cols', $cols)->setAttribute('rows', $rows); + } + + + /** + * Adds input for email. + * @param string control name + * @param string label + * @return Controls\TextInput + */ + public function addEmail($name, $label = NULL) + { + return $this[$name] = (new Controls\TextInput($label)) + ->setRequired(FALSE) + ->addRule(Form::EMAIL); + } + + + /** + * Adds input for integer. + * @param string control name + * @param string label + * @return Controls\TextInput + */ + public function addInteger($name, $label = NULL) + { + return $this[$name] = (new Controls\TextInput($label)) + ->setNullable() + ->setRequired(FALSE) + ->addRule(Form::INTEGER); } @@ -280,7 +310,7 @@ * @param string control name * @param string label * @param bool allows to upload multiple files - * @return Nette\Forms\Controls\UploadControl + * @return Controls\UploadControl */ public function addUpload($name, $label = NULL, $multiple = FALSE) { @@ -292,7 +322,7 @@ * Adds control that allows the user to upload multiple files. * @param string control name * @param string label - * @return Nette\Forms\Controls\UploadControl + * @return Controls\UploadControl */ public function addMultiUpload($name, $label = NULL) { @@ -304,13 +334,12 @@ * Adds hidden form control used to store a non-displayed value. * @param string control name * @param mixed default value - * @return Nette\Forms\Controls\HiddenField + * @return Controls\HiddenField */ public function addHidden($name, $default = NULL) { - $control = new Controls\HiddenField; - $control->setDefaultValue($default); - return $this[$name] = $control; + return $this[$name] = (new Controls\HiddenField) + ->setDefaultValue($default); } @@ -318,7 +347,7 @@ * Adds check box control to the form. * @param string control name * @param string caption - * @return Nette\Forms\Controls\Checkbox + * @return Controls\Checkbox */ public function addCheckbox($name, $caption = NULL) { @@ -331,7 +360,7 @@ * @param string control name * @param string label * @param array options from which to choose - * @return Nette\Forms\Controls\RadioList + * @return Controls\RadioList */ public function addRadioList($name, $label = NULL, array $items = NULL) { @@ -341,7 +370,7 @@ /** * Adds set of checkbox controls to the form. - * @return Nette\Forms\Controls\CheckboxList + * @return Controls\CheckboxList */ public function addCheckboxList($name, $label = NULL, array $items = NULL) { @@ -355,15 +384,12 @@ * @param string label * @param array items from which to choose * @param int number of rows that should be visible - * @return Nette\Forms\Controls\SelectBox + * @return Controls\SelectBox */ public function addSelect($name, $label = NULL, array $items = NULL, $size = NULL) { - $control = new Controls\SelectBox($label, $items); - if ($size > 1) { - $control->setAttribute('size', (int) $size); - } - return $this[$name] = $control; + return $this[$name] = (new Controls\SelectBox($label, $items)) + ->setAttribute('size', $size > 1 ? (int) $size : NULL); } @@ -373,15 +399,12 @@ * @param string label * @param array options from which to choose * @param int number of rows that should be visible - * @return Nette\Forms\Controls\MultiSelectBox + * @return Controls\MultiSelectBox */ public function addMultiSelect($name, $label = NULL, array $items = NULL, $size = NULL) { - $control = new Controls\MultiSelectBox($label, $items); - if ($size > 1) { - $control->setAttribute('size', (int) $size); - } - return $this[$name] = $control; + return $this[$name] = (new Controls\MultiSelectBox($label, $items)) + ->setAttribute('size', $size > 1 ? (int) $size : NULL); } @@ -389,7 +412,7 @@ * Adds button used to submit form. * @param string control name * @param string caption - * @return Nette\Forms\Controls\SubmitButton + * @return Controls\SubmitButton */ public function addSubmit($name, $caption = NULL) { @@ -401,7 +424,7 @@ * Adds push buttons with no default behavior. * @param string control name * @param string caption - * @return Nette\Forms\Controls\Button + * @return Controls\Button */ public function addButton($name, $caption = NULL) { @@ -414,7 +437,7 @@ * @param string control name * @param string URI of the image * @param string alternate text for the image - * @return Nette\Forms\Controls\ImageButton + * @return Controls\ImageButton */ public function addImage($name, $src = NULL, $alt = NULL) { @@ -431,10 +454,34 @@ { $control = new self; $control->currentGroup = $this->currentGroup; + if ($this->currentGroup !== NULL) { + $this->currentGroup->add($control); + } return $this[$name] = $control; } + /********************* extension methods ****************d*g**/ + + + public function __call($name, $args) + { + if ($callback = Nette\Utils\ObjectMixin::getExtensionMethod(__CLASS__, $name)) { + return Nette\Utils\Callback::invoke($callback, $this, ...$args); + } + return parent::__call($name, $args); + } + + + public static function extensionMethod($name, $callback = NULL) + { + if (strpos($name, '::') !== FALSE) { // back compatibility + list(, $name) = explode('::', $name); + } + Nette\Utils\ObjectMixin::setExtensionMethod(__CLASS__, $name, $callback); + } + + /********************* interface \ArrayAccess ****************d*g**/ diff -Nru php-nette-2.3.10/Nette/Forms/ControlGroup.php php-nette-2.4-20160731/Nette/Forms/ControlGroup.php --- php-nette-2.3.10/Nette/Forms/ControlGroup.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/ControlGroup.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,13 +13,15 @@ /** * A user group of form controls. */ -class ControlGroup extends Nette\Object +class ControlGroup { + use Nette\SmartObject; + /** @var \SplObjectStorage */ protected $controls; /** @var array user options */ - private $options = array(); + private $options = []; public function __construct() @@ -31,18 +33,22 @@ /** * @return self */ - public function add() + public function add(...$items) { - foreach (func_get_args() as $num => $item) { + foreach ($items as $item) { if ($item instanceof IControl) { $this->controls->attach($item); + } elseif ($item instanceof Container) { + foreach ($item->getComponents() as $component) { + $this->add($component); + } } elseif ($item instanceof \Traversable || is_array($item)) { - call_user_func_array(array($this, 'add'), is_array($item) ? $item : iterator_to_array($item)); + $this->add(...$item); } else { $type = is_object($item) ? get_class($item) : gettype($item); - throw new Nette\InvalidArgumentException("IControl items expected, $type given."); + throw new Nette\InvalidArgumentException("IControl or Container items expected, $type given."); } } return $this; diff -Nru php-nette-2.3.10/Nette/Forms/Controls/BaseControl.php php-nette-2.4-20160731/Nette/Forms/Controls/BaseControl.php --- php-nette-2.3.10/Nette/Forms/Controls/BaseControl.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/BaseControl.php 2016-07-31 17:46:36.000000000 +0000 @@ -8,9 +8,10 @@ namespace Nette\Forms\Controls; use Nette; +use Nette\Forms\Form; use Nette\Forms\IControl; +use Nette\Forms\Rules; use Nette\Utils\Html; -use Nette\Forms\Form; /** @@ -27,7 +28,10 @@ * @property-read Html $controlPrototype * @property-read Html $labelPrototype * @property bool $required + * @property-read bool $filled * @property-read array $errors + * @property-read array $options + * @property-read string $error */ abstract class BaseControl extends Nette\ComponentModel\Component implements IControl { @@ -47,22 +51,25 @@ protected $label; /** @var array */ - private $errors = array(); + private $errors = []; /** @var bool */ protected $disabled = FALSE; - /** @var bool */ - private $omitted = FALSE; + /** @var bool|NULL */ + private $omitted; - /** @var Nette\Forms\Rules */ + /** @var Rules */ private $rules; /** @var Nette\Localization\ITranslator */ private $translator = TRUE; // means autodetect /** @var array user options */ - private $options = array(); + private $options = []; + + /** @var bool */ + private static $autoOptional = FALSE; /** @@ -70,12 +77,15 @@ */ public function __construct($caption = NULL) { - $this->monitor('Nette\Forms\Form'); + $this->monitor(Form::class); parent::__construct(); - $this->control = Html::el('input', array('type' => NULL, 'name' => NULL)); + $this->control = Html::el('input', ['type' => NULL, 'name' => NULL]); $this->label = Html::el('label'); $this->caption = $caption; - $this->rules = new Nette\Forms\Rules($this); + $this->rules = new Rules($this); + if (self::$autoOptional) { + $this->setRequired(FALSE); + } $this->setValue(NULL); } @@ -100,7 +110,7 @@ */ public function getForm($need = TRUE) { - return $this->lookup('Nette\Forms\Form', $need); + return $this->lookup(Form::class, $need); } @@ -118,7 +128,7 @@ * Loads HTTP data. * @return mixed */ - public function getHttpData($type, $htmlTail = NULL) + protected function getHttpData($type, $htmlTail = NULL) { return $this->getForm()->getHttpData($type, $this->getHtmlName() . $htmlTail); } @@ -130,7 +140,7 @@ */ public function getHtmlName() { - return Nette\Forms\Helpers::generateHtmlName($this->lookupPath('Nette\Forms\Form')); + return Nette\Forms\Helpers::generateHtmlName($this->lookupPath(Form::class)); } @@ -140,6 +150,7 @@ /** * Sets control's value. * @return self + * @internal */ public function setValue($value) { @@ -165,7 +176,7 @@ public function isFilled() { $value = $this->getValue(); - return $value !== NULL && $value !== array() && $value !== ''; + return $value !== NULL && $value !== [] && $value !== ''; } @@ -191,7 +202,6 @@ public function setDisabled($value = TRUE) { if ($this->disabled = (bool) $value) { - $this->omitted = TRUE; $this->setValue(NULL); } return $this; @@ -226,7 +236,7 @@ */ public function isOmitted() { - return $this->omitted; + return $this->omitted || ($this->isDisabled() && $this->omitted === NULL); } @@ -241,13 +251,13 @@ { $this->setOption('rendered', TRUE); $el = clone $this->control; - return $el->addAttributes(array( + return $el->addAttributes([ 'name' => $this->getHtmlName(), 'id' => $this->getHtmlId(), 'required' => $this->isRequired(), 'disabled' => $this->isDisabled(), 'data-nette-rules' => Nette\Forms\Helpers::exportRules($this->rules) ?: NULL, - )); + ]); } @@ -266,6 +276,24 @@ /** + * @return Nette\Utils\Html|NULL + */ + public function getControlPart() + { + return $this->getControl(); + } + + + /** + * @return Nette\Utils\Html|NULL + */ + public function getLabelPart() + { + return $this->getLabel(); + } + + + /** * Returns control's HTML element template. * @return Html */ @@ -359,7 +387,7 @@ public function translate($value, $count = NULL) { if ($translator = $this->getTranslator()) { - $tmp = is_array($value) ? array(& $value) : array(array(& $value)); + $tmp = is_array($value) ? [& $value] : [[& $value]]; foreach ($tmp[0] as & $v) { if ($v != NULL && !$v instanceof Html) { // intentionally == $v = $translator->translate($v, $count); @@ -391,7 +419,7 @@ * Adds a validation condition a returns new branch. * @param mixed condition type * @param mixed optional condition arguments - * @return Nette\Forms\Rules new branch + * @return Rules new branch */ public function addCondition($validator, $value = NULL) { @@ -404,7 +432,7 @@ * @param IControl form control * @param mixed condition type * @param mixed optional condition arguments - * @return Nette\Forms\Rules new branch + * @return Rules new branch */ public function addConditionOn(IControl $control, $validator, $value = NULL) { @@ -413,7 +441,7 @@ /** - * @return Nette\Forms\Rules + * @return Rules */ public function getRules() { @@ -502,15 +530,17 @@ */ public function cleanErrors() { - $this->errors = array(); + $this->errors = []; } - /** @deprecated */ - protected static function exportRules($rules) + /** + * Globally enables new required/optional behavior. + * This method will be deprecated in next version. + */ + public static function enableAutoOptionalMode() { - trigger_error(__METHOD__ . '() is deprecated; use Nette\Forms\Helpers::exportRules() instead.', E_USER_DEPRECATED); - return Nette\Forms\Helpers::exportRules($rules); + self::$autoOptional = TRUE; } @@ -551,4 +581,25 @@ return $this->options; } + + /********************* extension methods ****************d*g**/ + + + public function __call($name, $args) + { + if ($callback = Nette\Utils\ObjectMixin::getExtensionMethod(get_class($this), $name)) { + return Nette\Utils\Callback::invoke($callback, $this, ...$args); + } + return parent::__call($name, $args); + } + + + public static function extensionMethod($name, $callback = NULL) + { + if (strpos($name, '::') !== FALSE) { // back compatibility + list(, $name) = explode('::', $name); + } + Nette\Utils\ObjectMixin::setExtensionMethod(get_called_class(), $name, $callback); + } + } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/Button.php php-nette-2.4-20160731/Nette/Forms/Controls/Button.php --- php-nette-2.3.10/Nette/Forms/Controls/Button.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/Button.php 2016-07-31 17:46:36.000000000 +0000 @@ -23,6 +23,7 @@ { parent::__construct($caption); $this->control->type = 'button'; + $this->setOption('type', 'button'); } @@ -33,7 +34,7 @@ public function isFilled() { $value = $this->getValue(); - return $value !== NULL && $value !== array(); + return $value !== NULL && $value !== []; } @@ -56,11 +57,11 @@ { $this->setOption('rendered', TRUE); $el = clone $this->control; - return $el->addAttributes(array( + return $el->addAttributes([ 'name' => $this->getHtmlName(), 'disabled' => $this->isDisabled(), 'value' => $this->translate($caption === NULL ? $this->caption : $caption), - )); + ]); } } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/CheckboxList.php php-nette-2.4-20160731/Nette/Forms/Controls/CheckboxList.php --- php-nette-2.3.10/Nette/Forms/Controls/CheckboxList.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/CheckboxList.php 2016-07-31 17:46:36.000000000 +0000 @@ -15,41 +15,54 @@ * Set of checkboxes. * * @property-read Html $separatorPrototype + * @property-read Html $containerPrototype */ class CheckboxList extends MultiChoiceControl { /** @var Html separator element template */ protected $separator; + /** @var Html container element template */ + protected $container; + + /** + * @param string label + * @param array options from which to choose + */ public function __construct($label = NULL, array $items = NULL) { parent::__construct($label, $items); $this->control->type = 'checkbox'; + $this->container = Html::el(); $this->separator = Html::el('br'); + $this->setOption('type', 'checkbox'); } /** * Generates control's HTML element. - * @return string + * @return Html */ public function getControl() { + $input = parent::getControl(); $items = $this->getItems(); reset($items); - $input = parent::getControl(); - return Nette\Forms\Helpers::createInputList( - $this->translate($items), - array_merge($input->attrs, array( - 'id' => NULL, - 'checked?' => $this->value, - 'disabled:' => $this->disabled, - 'required' => NULL, - 'data-nette-rules:' => array(key($items) => $input->attrs['data-nette-rules']), - )), - $this->label->attrs, - $this->separator + + return $this->container->setHtml( + Nette\Forms\Helpers::createInputList( + $this->translate($items), + array_merge($input->attrs, [ + 'id' => NULL, + 'checked?' => $this->value, + 'disabled:' => $this->disabled, + 'required' => NULL, + 'data-nette-rules:' => [key($items) => $input->attrs['data-nette-rules']], + ]), + $this->label->attrs, + $this->separator + ) ); } @@ -66,28 +79,18 @@ /** - * Returns separator HTML element template. - * @return Html - */ - public function getSeparatorPrototype() - { - return $this->separator; - } - - - /** * @return Html */ - public function getControlPart($key) + public function getControlPart($key = NULL) { - $key = key(array((string) $key => NULL)); - return parent::getControl()->addAttributes(array( + $key = key([(string) $key => NULL]); + return parent::getControl()->addAttributes([ 'id' => $this->getHtmlId() . '-' . $key, 'checked' => in_array($key, (array) $this->value, TRUE), 'disabled' => is_array($this->disabled) ? isset($this->disabled[$key]) : $this->disabled, 'required' => NULL, 'value' => $key, - )); + ]); } @@ -101,4 +104,24 @@ : $this->getLabel(); } + + /** + * Returns separator HTML element template. + * @return Html + */ + public function getSeparatorPrototype() + { + return $this->separator; + } + + + /** + * Returns container HTML element template. + * @return Html + */ + public function getContainerPrototype() + { + return $this->container; + } + } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/Checkbox.php php-nette-2.4-20160731/Nette/Forms/Controls/Checkbox.php --- php-nette-2.3.10/Nette/Forms/Controls/Checkbox.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/Checkbox.php 2016-07-31 17:46:36.000000000 +0000 @@ -27,6 +27,7 @@ parent::__construct($label); $this->control->type = 'checkbox'; $this->wrapper = Nette\Utils\Html::el(); + $this->setOption('type', 'checkbox'); } @@ -34,6 +35,7 @@ * Sets control's value. * @param bool * @return self + * @internal */ public function setValue($value) { diff -Nru php-nette-2.3.10/Nette/Forms/Controls/ChoiceControl.php php-nette-2.4-20160731/Nette/Forms/Controls/ChoiceControl.php --- php-nette-2.3.10/Nette/Forms/Controls/ChoiceControl.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/ChoiceControl.php 2016-07-31 17:46:36.000000000 +0000 @@ -22,7 +22,7 @@ public $checkAllowedValues = TRUE; /** @var array */ - private $items = array(); + private $items = []; public function __construct($label = NULL, array $items = NULL) @@ -45,7 +45,7 @@ if (is_array($this->disabled) && isset($this->disabled[$this->value])) { $this->value = NULL; } else { - $this->value = key(array($this->value => NULL)); + $this->value = key([$this->value => NULL]); } } } @@ -55,6 +55,7 @@ * Sets selected item (by key). * @param string|int * @return self + * @internal */ public function setValue($value) { @@ -62,7 +63,7 @@ $set = Nette\Utils\Strings::truncate(implode(', ', array_map(function ($s) { return var_export($s, TRUE); }, array_keys($this->items))), 70, '...'); throw new Nette\InvalidArgumentException("Value '$value' is out of allowed set [$set] in field '{$this->name}'."); } - $this->value = $value === NULL ? NULL : key(array((string) $value => NULL)); + $this->value = $value === NULL ? NULL : key([(string) $value => NULL]); return $this; } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/CsrfProtection.php php-nette-2.4-20160731/Nette/Forms/Controls/CsrfProtection.php --- php-nette-2.3.10/Nette/Forms/Controls/CsrfProtection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/CsrfProtection.php 2016-07-31 17:46:36.000000000 +0000 @@ -28,8 +28,10 @@ public function __construct($message) { parent::__construct(); - $this->setOmitted()->addRule(self::PROTECTION, $message); - $this->monitor('Nette\Application\UI\Presenter'); + $this->setOmitted() + ->setRequired() + ->addRule(self::PROTECTION, $message); + $this->monitor(Nette\Application\UI\Presenter::class); } @@ -44,6 +46,7 @@ /** * @return self + * @internal */ public function setValue($value) { diff -Nru php-nette-2.3.10/Nette/Forms/Controls/HiddenField.php php-nette-2.4-20160731/Nette/Forms/Controls/HiddenField.php --- php-nette-2.3.10/Nette/Forms/Controls/HiddenField.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/HiddenField.php 2016-07-31 17:46:36.000000000 +0000 @@ -23,8 +23,9 @@ { parent::__construct(); $this->control->type = 'hidden'; + $this->setOption('type', 'hidden'); if ($persistentValue !== NULL) { - $this->unmonitor('Nette\Forms\Form'); + $this->unmonitor(Nette\Forms\Form::class); $this->persistValue = TRUE; $this->value = (string) $persistentValue; } @@ -35,6 +36,7 @@ * Sets control's value. * @param string * @return self + * @internal */ public function setValue($value) { @@ -56,11 +58,11 @@ { $this->setOption('rendered', TRUE); $el = clone $this->control; - return $el->addAttributes(array( + return $el->addAttributes([ 'name' => $this->getHtmlName(), 'disabled' => $this->isDisabled(), 'value' => $this->value, - )); + ]); } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/ImageButton.php php-nette-2.4-20160731/Nette/Forms/Controls/ImageButton.php --- php-nette-2.3.10/Nette/Forms/Controls/ImageButton.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/ImageButton.php 2016-07-31 17:46:36.000000000 +0000 @@ -35,7 +35,7 @@ { parent::loadHttpData(); $this->value = $this->value - ? array((int) array_shift($this->value), (int) array_shift($this->value)) + ? [(int) array_shift($this->value), (int) array_shift($this->value)] : NULL; } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/MultiChoiceControl.php php-nette-2.4-20160731/Nette/Forms/Controls/MultiChoiceControl.php --- php-nette-2.3.10/Nette/Forms/Controls/MultiChoiceControl.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/MultiChoiceControl.php 2016-07-31 17:46:36.000000000 +0000 @@ -22,7 +22,7 @@ public $checkAllowedValues = TRUE; /** @var array */ - private $items = array(); + private $items = []; public function __construct($label = NULL, array $items = NULL) @@ -51,6 +51,7 @@ * Sets selected items (by keys). * @param array * @return self + * @internal */ public function setValue($values) { @@ -59,7 +60,7 @@ } elseif (!is_array($values)) { throw new Nette\InvalidArgumentException(sprintf("Value must be array or NULL, %s given in field '%s'.", gettype($values), $this->name)); } - $flip = array(); + $flip = []; foreach ($values as $value) { if (!is_scalar($value) && !method_exists($value, '__toString')) { throw new Nette\InvalidArgumentException(sprintf("Values must be scalar, %s given in field '%s'.", gettype($value), $this->name)); @@ -103,7 +104,7 @@ */ public function isFilled() { - return $this->getValue() !== array(); + return $this->getValue() !== []; } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/MultiSelectBox.php php-nette-2.4-20160731/Nette/Forms/Controls/MultiSelectBox.php --- php-nette-2.3.10/Nette/Forms/Controls/MultiSelectBox.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/MultiSelectBox.php 2016-07-31 17:46:36.000000000 +0000 @@ -16,7 +16,17 @@ class MultiSelectBox extends MultiChoiceControl { /** @var array of option / optgroup */ - private $options = array(); + private $options = []; + + /** @var array */ + private $optionAttributes = []; + + + public function __construct($label = NULL, array $items = NULL) + { + parent::__construct($label, $items); + $this->setOption('type', 'select'); + } /** @@ -26,7 +36,7 @@ public function setItems(array $items, $useKeys = TRUE) { if (!$useKeys) { - $res = array(); + $res = []; foreach ($items as $key => $value) { unset($items[$key]); if (is_array($value)) { @@ -50,18 +60,28 @@ */ public function getControl() { - $items = array(); + $items = []; foreach ($this->options as $key => $value) { $items[is_array($value) ? $this->translate($key) : $key] = $this->translate($value); } return Nette\Forms\Helpers::createSelectBox( $items, - array( + [ 'selected?' => $this->value, 'disabled:' => is_array($this->disabled) ? $this->disabled : NULL, - ) + ] + $this->optionAttributes )->addAttributes(parent::getControl()->attrs)->multiple(TRUE); } + + /** + * @return self + */ + public function addOptionAttributes(array $attributes) + { + $this->optionAttributes = $attributes + $this->optionAttributes; + return $this; + } + } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/RadioList.php php-nette-2.4-20160731/Nette/Forms/Controls/RadioList.php --- php-nette-2.3.10/Nette/Forms/Controls/RadioList.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/RadioList.php 2016-07-31 17:46:36.000000000 +0000 @@ -44,6 +44,7 @@ $this->container = Html::el(); $this->separator = Html::el('br'); $this->itemLabel = Html::el(); + $this->setOption('type', 'radio'); } @@ -55,7 +56,7 @@ { $input = parent::getControl(); $items = $this->getItems(); - $ids = array(); + $ids = []; if ($this->generateId) { foreach ($items as $value => $label) { $ids[$value] = $input->id . '-' . $value; @@ -65,13 +66,13 @@ return $this->container->setHtml( Nette\Forms\Helpers::createInputList( $this->translate($items), - array_merge($input->attrs, array( + array_merge($input->attrs, [ 'id:' => $ids, 'checked?' => $this->value, 'disabled:' => $this->disabled, - 'data-nette-rules:' => array(key($items) => $input->attrs['data-nette-rules']), - )), - array('for:' => $ids) + $this->itemLabel->attrs, + 'data-nette-rules:' => [key($items) => $input->attrs['data-nette-rules']], + ]), + ['for:' => $ids] + $this->itemLabel->attrs, $this->separator ) ); @@ -92,15 +93,15 @@ /** * @return Html */ - public function getControlPart($key) + public function getControlPart($key = NULL) { - $key = key(array((string) $key => NULL)); - return parent::getControl()->addAttributes(array( + $key = key([(string) $key => NULL]); + return parent::getControl()->addAttributes([ 'id' => $this->getHtmlId() . '-' . $key, 'checked' => in_array($key, (array) $this->value, TRUE), 'disabled' => is_array($this->disabled) ? isset($this->disabled[$key]) : $this->disabled, 'value' => $key, - )); + ]); } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/SelectBox.php php-nette-2.4-20160731/Nette/Forms/Controls/SelectBox.php --- php-nette-2.3.10/Nette/Forms/Controls/SelectBox.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/SelectBox.php 2016-07-31 17:46:36.000000000 +0000 @@ -19,11 +19,23 @@ const VALID = ':selectBoxValid'; /** @var array of option / optgroup */ - private $options = array(); + private $options = []; /** @var mixed */ private $prompt = FALSE; + /** @var array */ + private $optionAttributes = []; + + + public function __construct($label = NULL, array $items = NULL) + { + parent::__construct($label, $items); + $this->setOption('type', 'select'); + $this->addCondition(Nette\Forms\Form::BLANK) + ->addRule([$this, 'isOk'], Nette\Forms\Validator::$messages[self::VALID]); + } + /** * Sets first prompt item in select box. @@ -54,7 +66,7 @@ public function setItems(array $items, $useKeys = TRUE) { if (!$useKeys) { - $res = array(); + $res = []; foreach ($items as $key => $value) { unset($items[$key]); if (is_array($value)) { @@ -78,31 +90,41 @@ */ public function getControl() { - $items = $this->prompt === FALSE ? array() : array('' => $this->translate($this->prompt)); + $items = $this->prompt === FALSE ? [] : ['' => $this->translate($this->prompt)]; foreach ($this->options as $key => $value) { $items[is_array($value) ? $this->translate($key) : $key] = $this->translate($value); } return Nette\Forms\Helpers::createSelectBox( $items, - array( + [ 'selected?' => $this->value, 'disabled:' => is_array($this->disabled) ? $this->disabled : NULL, - ) + ] + $this->optionAttributes )->addAttributes(parent::getControl()->attrs); } /** - * Performs the server side validation. - * @return void + * @return self */ - public function validate() + public function addOptionAttributes(array $attributes) { - parent::validate(); - if (!$this->isDisabled() && $this->prompt === FALSE && $this->getValue() === NULL && $this->options && $this->control->size < 2) { - $this->addError(Nette\Forms\Validator::$messages[self::VALID]); - } + $this->optionAttributes = $attributes + $this->optionAttributes; + return $this; + } + + + /** + * @return bool + */ + public function isOk() + { + return $this->isDisabled() + || $this->prompt !== FALSE + || $this->getValue() !== NULL + || !$this->options + || $this->control->size > 1; } } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/SubmitButton.php php-nette-2.4-20160731/Nette/Forms/Controls/SubmitButton.php --- php-nette-2.3.10/Nette/Forms/Controls/SubmitButton.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/SubmitButton.php 2016-07-31 17:46:36.000000000 +0000 @@ -70,8 +70,8 @@ if ($scope === NULL || $scope === TRUE) { $this->validationScope = NULL; } else { - $this->validationScope = array(); - foreach ($scope ?: array() as $control) { + $this->validationScope = []; + foreach ($scope ?: [] as $control) { if (!$control instanceof Nette\Forms\Container && !$control instanceof Nette\Forms\IControl) { throw new Nette\InvalidArgumentException('Validation scope accepts only Nette\Forms\Container or Nette\Forms\IControl instances.'); } @@ -109,14 +109,14 @@ */ public function getControl($caption = NULL) { - $scope = array(); + $scope = []; foreach ((array) $this->validationScope as $control) { - $scope[] = $control->lookupPath('Nette\Forms\Form'); + $scope[] = $control->lookupPath(Nette\Forms\Form::class); } - return parent::getControl($caption)->addAttributes(array( + return parent::getControl($caption)->addAttributes([ 'formnovalidate' => $this->validationScope !== NULL, 'data-nette-validation-scope' => $scope ?: NULL, - )); + ]); } } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/TextArea.php php-nette-2.4-20160731/Nette/Forms/Controls/TextArea.php --- php-nette-2.3.10/Nette/Forms/Controls/TextArea.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/TextArea.php 2016-07-31 17:46:36.000000000 +0000 @@ -23,6 +23,7 @@ { parent::__construct($label); $this->control->setName('textarea'); + $this->setOption('type', 'textarea'); } @@ -32,12 +33,8 @@ */ public function getControl() { - $value = $this->getValue(); - if ($value === '') { - $value = $this->translate($this->emptyValue); - } return parent::getControl() - ->setText($value); + ->setText($this->getRenderedValue()); } } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/TextBase.php php-nette-2.4-20160731/Nette/Forms/Controls/TextBase.php --- php-nette-2.3.10/Nette/Forms/Controls/TextBase.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/TextBase.php 2016-07-31 17:46:36.000000000 +0000 @@ -23,11 +23,15 @@ /** @var mixed unfiltered submitted value */ protected $rawValue = ''; + /** @var bool */ + private $nullable; + /** * Sets control's value. * @param string * @return self + * @internal */ public function setValue($value) { @@ -47,7 +51,19 @@ */ public function getValue() { - return $this->value === Strings::trim($this->translate($this->emptyValue)) ? '' : $this->value; + return $this->nullable && $this->value === '' ? NULL : $this->value; + } + + + /** + * Sets whether getValue() returns NULL instead of empty string. + * @param bool + * @return self + */ + public function setNullable($value = TRUE) + { + $this->nullable = (bool) $value; + return $this; } @@ -92,7 +108,7 @@ */ public function addFilter($filter) { - $this->rules->addFilter($filter); + $this->getRules()->addFilter($filter); return $this; } @@ -110,6 +126,17 @@ } + /** + * @return string|NULL + */ + protected function getRenderedValue() + { + return $this->rawValue === '' + ? ($this->emptyValue === '' ? NULL : $this->translate($this->emptyValue)) + : $this->rawValue; + } + + public function addRule($validator, $message = NULL, $arg = NULL) { if ($validator === Form::LENGTH || $validator === Form::MAX_LENGTH) { @@ -121,4 +148,17 @@ return parent::addRule($validator, $message, $arg); } + + /** + * Performs the server side validation. + * @return void + */ + public function validate() + { + if ($this->value === Strings::trim($this->translate($this->emptyValue))) { + $this->value = ''; + } + parent::validate(); + } + } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/TextInput.php php-nette-2.4-20160731/Nette/Forms/Controls/TextInput.php --- php-nette-2.3.10/Nette/Forms/Controls/TextInput.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/TextInput.php 2016-07-31 17:46:36.000000000 +0000 @@ -24,8 +24,8 @@ public function __construct($label = NULL, $maxLength = NULL) { parent::__construct($label); - $this->control->type = 'text'; $this->control->maxlength = $maxLength; + $this->setOption('type', 'text'); } @@ -57,41 +57,43 @@ */ public function getControl() { - $input = parent::getControl(); + return parent::getControl()->addAttributes([ + 'value' => $this->control->type === 'password' ? $this->control->value : $this->getRenderedValue(), + 'type' => $this->control->type ?: 'text', + ]); + } - foreach ($this->getRules() as $rule) { - if ($rule->isNegative || $rule->branch) { - } elseif (in_array($rule->validator, array(Form::MIN, Form::MAX, Form::RANGE), TRUE) - && in_array($input->type, array('number', 'range', 'datetime-local', 'datetime', 'date', 'month', 'week', 'time'), TRUE) - ) { - if ($rule->validator === Form::MIN) { - $range = array($rule->arg, NULL); - } elseif ($rule->validator === Form::MAX) { - $range = array(NULL, $rule->arg); - } else { - $range = $rule->arg; - } - if (isset($range[0]) && is_scalar($range[0])) { - $input->min = isset($input->min) ? max($input->min, $range[0]) : $range[0]; - } - if (isset($range[1]) && is_scalar($range[1])) { - $input->max = isset($input->max) ? min($input->max, $range[1]) : $range[1]; - } - - } elseif ($rule->validator === Form::PATTERN && is_scalar($rule->arg) - && in_array($input->type, array('text', 'search', 'tel', 'url', 'email', 'password'), TRUE) - ) { - $input->pattern = $rule->arg; + public function addRule($validator, $message = NULL, $arg = NULL) + { + if ($this->control->type === NULL && in_array($validator, [Form::EMAIL, Form::URL, Form::INTEGER], TRUE)) { + static $types = [Form::EMAIL => 'email', Form::URL => 'url', Form::INTEGER => 'number']; + $this->control->type = $types[$validator]; + + } elseif (in_array($validator, [Form::MIN, Form::MAX, Form::RANGE], TRUE) + && in_array($this->control->type, ['number', 'range', 'datetime-local', 'datetime', 'date', 'month', 'week', 'time'], TRUE) + ) { + if ($validator === Form::MIN) { + $range = [$arg, NULL]; + } elseif ($validator === Form::MAX) { + $range = [NULL, $arg]; + } else { + $range = $arg; + } + if (isset($range[0]) && is_scalar($range[0])) { + $this->control->min = isset($this->control->min) ? max($this->control->min, $range[0]) : $range[0]; + } + if (isset($range[1]) && is_scalar($range[1])) { + $this->control->max = isset($this->control->max) ? min($this->control->max, $range[1]) : $range[1]; } - } - if ($input->type !== 'password' && ($this->rawValue !== '' || $this->emptyValue !== '')) { - $input->value = $this->rawValue === '' - ? $this->translate($this->emptyValue) - : $this->rawValue; + } elseif ($validator === Form::PATTERN && is_scalar($arg) + && in_array($this->control->type, [NULL, 'text', 'search', 'tel', 'url', 'email', 'password'], TRUE) + ) { + $this->control->pattern = $arg; } - return $input; + + return parent::addRule($validator, $message, $arg); } } diff -Nru php-nette-2.3.10/Nette/Forms/Controls/UploadControl.php php-nette-2.4-20160731/Nette/Forms/Controls/UploadControl.php --- php-nette-2.3.10/Nette/Forms/Controls/UploadControl.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Controls/UploadControl.php 2016-07-31 17:46:36.000000000 +0000 @@ -8,6 +8,7 @@ namespace Nette\Forms\Controls; use Nette; +use Nette\Forms; use Nette\Http\FileUpload; @@ -16,6 +17,8 @@ */ class UploadControl extends BaseControl { + /** validation rule */ + const VALID = ':uploadControlValid'; /** * @param string label @@ -26,6 +29,9 @@ parent::__construct($label); $this->control->type = 'file'; $this->control->multiple = (bool) $multiple; + $this->setOption('type', 'file'); + $this->addCondition(Forms\Form::FILLED) + ->addRule([$this, 'isOk'], Forms\Validator::$messages[self::VALID]); } @@ -38,7 +44,7 @@ protected function attached($form) { if ($form instanceof Nette\Forms\Form) { - if ($form->getMethod() !== Nette\Forms\Form::POST) { + if (!$form->isMethod('post')) { throw new Nette\InvalidStateException('File upload requires method POST.'); } $form->getElementPrototype()->enctype = 'multipart/form-data'; @@ -72,6 +78,7 @@ /** * @return self + * @internal */ public function setValue($value) { @@ -85,7 +92,23 @@ */ public function isFilled() { - return $this->value instanceof FileUpload ? $this->value->isOk() : (bool) $this->value; // ignore NULL object + return $this->value instanceof FileUpload + ? $this->value->getError() !== UPLOAD_ERR_NO_FILE // ignore NULL object + : (bool) $this->value; + } + + + /** + * Have been all files succesfully uploaded? + * @return bool + */ + public function isOk() + { + return $this->value instanceof FileUpload + ? $this->value->isOk() + : $this->value && array_reduce($this->value, function ($carry, $fileUpload) { + return $carry && $fileUpload->isOk(); + }, TRUE); } } diff -Nru php-nette-2.3.10/Nette/Forms/Form.php php-nette-2.4-20160731/Nette/Forms/Form.php --- php-nette-2.3.10/Nette/Forms/Form.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Form.php 2016-07-31 17:46:36.000000000 +0000 @@ -14,7 +14,11 @@ * Creates, validates and renders HTML forms. * * @property-read array $errors + * @property-read array $ownErrors * @property-read Nette\Utils\Html $elementPrototype + * @property-read IFormRenderer $renderer + * @property string $action + * @property string $method */ class Form extends Container implements Nette\Utils\IHtmlString { @@ -82,6 +86,9 @@ /** @var callable[] function (Form $sender); Occurs when the form is submitted */ public $onSubmit; + /** @var callable[] function (Form $sender); Occurs before the form is rendered */ + public $onRender; + /** @var mixed or NULL meaning: not detected yet */ private $submittedBy; @@ -98,14 +105,17 @@ private $translator; /** @var ControlGroup[] */ - private $groups = array(); + private $groups = []; /** @var array */ - private $errors = array(); + private $errors = []; /** @var Nette\Http\IRequest used only by standalone form */ public $httpRequest; + /** @var bool */ + private $beforeRenderCalled; + /** * Form constructor. @@ -206,13 +216,26 @@ /** + * Checks if the request method is the given one. + * @param string + * @return bool + */ + public function isMethod($method) + { + return strcasecmp($this->getElementPrototype()->method, $method) === 0; + } + + + /** * Cross-Site Request Forgery (CSRF) form protection. * @param string * @return Controls\CsrfProtection */ public function addProtection($message = NULL) { - return $this[self::PROTECTOR_ID] = new Controls\CsrfProtection($message); + $control = new Controls\CsrfProtection($message); + $this->addComponent($control, self::PROTECTOR_ID, key($this->getComponents())); + return $control; } @@ -350,6 +373,7 @@ /** * Sets the submittor control. * @return self + * @internal */ public function setSubmittedBy(ISubmitterControl $by = NULL) { @@ -469,11 +493,11 @@ /** @internal */ public function validateMaxPostSize() { - if (!$this->submittedBy || strcasecmp($this->getMethod(), 'POST') || empty($_SERVER['CONTENT_LENGTH'])) { + if (!$this->submittedBy || !$this->isMethod('post') || empty($_SERVER['CONTENT_LENGTH'])) { return; } $maxSize = ini_get('post_max_size'); - $units = array('k' => 10, 'm' => 20, 'g' => 30); + $units = ['k' => 10, 'm' => 20, 'g' => 30]; if (isset($units[$ch = strtolower(substr($maxSize, -1))])) { $maxSize <<= $units[$ch]; } @@ -518,7 +542,7 @@ */ public function cleanErrors() { - $this->errors = array(); + $this->errors = []; } @@ -575,14 +599,38 @@ /** + * @return void + */ + protected function beforeRender() + { + } + + + /** + * Must be called before form is rendered and render() is not used. + * @return void + */ + public function fireRenderEvents() + { + if (!$this->beforeRenderCalled) { + foreach ($this->getComponents(TRUE, Controls\BaseControl::class) as $control) { + $control->getRules()->check(); + } + $this->beforeRenderCalled = TRUE; + $this->beforeRender(); + $this->onRender($this); + } + } + + + /** * Renders form. * @return void */ - public function render() + public function render(...$args) { - $args = func_get_args(); - array_unshift($args, $this); - echo call_user_func_array(array($this->getRenderer(), 'render'), $args); + $this->fireRenderEvents(); + echo $this->getRenderer()->render($this, ...$args); } @@ -594,6 +642,7 @@ public function __toString() { try { + $this->fireRenderEvents(); return $this->getRenderer()->render($this); } catch (\Throwable $e) { @@ -629,8 +678,8 @@ */ public function getToggles() { - $toggles = array(); - foreach ($this->getControls() as $control) { + $toggles = []; + foreach ($this->getComponents(TRUE, Controls\BaseControl::class) as $control) { $toggles = $control->getRules()->getToggleStates($toggles); } return $toggles; diff -Nru php-nette-2.3.10/Nette/Forms/Helpers.php php-nette-2.4-20160731/Nette/Forms/Helpers.php --- php-nette-2.3.10/Nette/Forms/Helpers.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Helpers.php 2016-07-31 17:46:36.000000000 +0000 @@ -15,12 +15,14 @@ /** * Forms helpers. */ -class Helpers extends Nette\Object +class Helpers { - private static $unsafeNames = array( + use Nette\StaticClass; + + private static $unsafeNames = [ 'attributes', 'children', 'elements', 'focus', 'length', 'reset', 'style', 'submit', 'onsubmit', 'form', 'presenter', 'action', - ); + ]; /** @@ -33,13 +35,13 @@ */ public static function extractHttpData(array $data, $htmlName, $type) { - $name = explode('[', str_replace(array('[]', ']', '.'), array('', '', '_'), $htmlName)); + $name = explode('[', str_replace(['[]', ']', '.'], ['', '', '_'], $htmlName)); $data = Nette\Utils\Arrays::get($data, $name, NULL); $itype = $type & ~Form::DATA_KEYS; if (substr($htmlName, -2) === '[]') { if (!is_array($data)) { - return array(); + return []; } foreach ($data as $k => $v) { $data[$k] = $v = static::sanitize($itype, $v); @@ -96,7 +98,7 @@ */ public static function exportRules(Rules $rules) { - $payload = array(); + $payload = []; foreach ($rules as $rule) { if (!is_string($op = $rule->validator)) { if (!Nette\Utils\Callback::isStatic($op)) { @@ -105,29 +107,34 @@ $op = Nette\Utils\Callback::toString($op); } if ($rule->branch) { - $item = array( + $item = [ 'op' => ($rule->isNegative ? '~' : '') . $op, 'rules' => static::exportRules($rule->branch), 'control' => $rule->control->getHtmlName(), - ); + ]; if ($rule->branch->getToggles()) { $item['toggle'] = $rule->branch->getToggles(); + } elseif (!$item['rules']) { + continue; } } else { - $item = array('op' => ($rule->isNegative ? '~' : '') . $op, 'msg' => Validator::formatMessage($rule, FALSE)); + $item = ['op' => ($rule->isNegative ? '~' : '') . $op, 'msg' => Validator::formatMessage($rule, FALSE)]; } if (is_array($rule->arg)) { - $item['arg'] = array(); + $item['arg'] = []; foreach ($rule->arg as $key => $value) { - $item['arg'][$key] = $value instanceof IControl ? array('control' => $value->getHtmlName()) : $value; + $item['arg'][$key] = $value instanceof IControl ? ['control' => $value->getHtmlName()] : $value; } } elseif ($rule->arg !== NULL) { - $item['arg'] = $rule->arg instanceof IControl ? array('control' => $rule->arg->getHtmlName()) : $rule->arg; + $item['arg'] = $rule->arg instanceof IControl ? ['control' => $rule->arg->getHtmlName()] : $rule->arg; } $payload[] = $item; } + if ($payload && $rules->isOptional()) { + array_unshift($payload, ['op' => 'optional']); + } return $payload; } @@ -142,7 +149,7 @@ $res = ''; $input = Html::el(); $label = Html::el(); - list($wrapper, $wrapperEnd) = $wrapper instanceof Html ? array($wrapper->startTag(), $wrapper->endTag()) : array((string) $wrapper, ''); + list($wrapper, $wrapperEnd) = $wrapper instanceof Html ? [$wrapper->startTag(), $wrapper->endTag()] : [(string) $wrapper, '']; foreach ($items as $value => $caption) { foreach ($inputAttrs as $k => $v) { @@ -179,7 +186,7 @@ $res .= Html::el('optgroup')->label($group)->startTag(); $tmp = ''; } else { - $subitems = array($group => $subitems); + $subitems = [$group => $subitems]; } foreach ($subitems as $value => $caption) { $option->value = $value; @@ -204,7 +211,7 @@ private static function prepareAttrs($attrs, $name) { - $dynamic = array(); + $dynamic = []; foreach ((array) $attrs as $k => $v) { $p = str_split($k, strlen($k) - 1); if ($p[1] === '?' || $p[1] === ':') { @@ -218,7 +225,7 @@ } } } - return array($dynamic, '<' . $name . Html::el(NULL, $attrs)->attributes()); + return [$dynamic, '<' . $name . Html::el(NULL, $attrs)->attributes()]; } } diff -Nru php-nette-2.3.10/Nette/Forms/IControl.php php-nette-2.4-20160731/Nette/Forms/IControl.php --- php-nette-2.3.10/Nette/Forms/IControl.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/IControl.php 2016-07-31 17:46:36.000000000 +0000 @@ -44,12 +44,4 @@ */ function isOmitted(); - /** - * Returns translated string. - * @param string - * @param int plural count - * @return string - */ - function translate($s, $count = NULL); - } diff -Nru php-nette-2.3.10/Nette/Forms/Rendering/DefaultFormRenderer.php php-nette-2.4-20160731/Nette/Forms/Rendering/DefaultFormRenderer.php --- php-nette-2.3.10/Nette/Forms/Rendering/DefaultFormRenderer.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Rendering/DefaultFormRenderer.php 2016-07-31 17:46:36.000000000 +0000 @@ -14,8 +14,10 @@ /** * Converts a Form into the HTML output. */ -class DefaultFormRenderer extends Nette\Object implements Nette\Forms\IFormRenderer +class DefaultFormRenderer implements Nette\Forms\IFormRenderer { + use Nette\SmartObject; + /** * /--- form.container * @@ -53,35 +55,35 @@ * \-- * * @var array of HTML tags */ - public $wrappers = array( - 'form' => array( + public $wrappers = [ + 'form' => [ 'container' => NULL, - ), + ], - 'error' => array( + 'error' => [ 'container' => 'ul class=error', 'item' => 'li', - ), + ], - 'group' => array( + 'group' => [ 'container' => 'fieldset', 'label' => 'legend', 'description' => 'p', - ), + ], - 'controls' => array( + 'controls' => [ 'container' => 'table', - ), + ], - 'pair' => array( + 'pair' => [ 'container' => 'tr', '.required' => 'required', '.optional' => NULL, '.odd' => NULL, '.error' => NULL, - ), + ], - 'control' => array( + 'control' => [ 'container' => 'td', '.odd' => NULL, @@ -94,21 +96,23 @@ '.text' => 'text', '.password' => 'text', '.file' => 'text', + '.email' => 'text', + '.number' => 'text', '.submit' => 'button', '.image' => 'imagebutton', '.button' => 'button', - ), + ], - 'label' => array( + 'label' => [ 'container' => 'th', 'suffix' => NULL, 'requiredsuffix' => '', - ), + ], - 'hidden' => array( - 'container' => 'div', - ), - ); + 'hidden' => [ + 'container' => NULL, + ], + ]; /** @var Nette\Forms\Form */ protected $form; @@ -161,7 +165,7 @@ $control->setOption('rendered', FALSE); } - if (strcasecmp($this->form->getMethod(), 'get') === 0) { + if ($this->form->isMethod('get')) { $el = clone $this->form->getElementPrototype(); $query = parse_url($el->action, PHP_URL_QUERY); $el->action = str_replace("?$query", '', $el->action); @@ -170,7 +174,7 @@ $parts = explode('=', $param, 2); $name = urldecode($parts[0]); if (!isset($this->form[$name])) { - $s .= Html::el('input', array('type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1]))); + $s .= Html::el('input', ['type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])]); } } return $el->startTag() . ($s ? "\n\t" . $this->getWrapper('hidden container')->setHtml($s) : ''); @@ -189,11 +193,11 @@ { $s = ''; foreach ($this->form->getControls() as $control) { - if ($control instanceof Nette\Forms\Controls\HiddenField && !$control->getOption('rendered')) { + if ($control->getOption('type') === 'hidden' && !$control->getOption('rendered')) { $s .= $control->getControl(); } } - if (iterator_count($this->form->getComponents(TRUE, 'Nette\Forms\Controls\TextInput')) < 2) { + if (iterator_count($this->form->getComponents(TRUE, Nette\Forms\Controls\TextInput::class)) < 2) { $s .= ''; } if ($s) { @@ -222,11 +226,11 @@ foreach ($errors as $error) { $item = clone $item; if ($error instanceof Html) { - $item->add($error); + $item->addHtml($error); } else { $item->setText($error); } - $container->add($item); + $container->addHtml($item); } return "\n" . $container->render($control ? 1 : 0); } @@ -260,7 +264,7 @@ $text = $group->getOption('label'); if ($text instanceof Html) { - $s .= $this->getWrapper('group label')->add($text); + $s .= $this->getWrapper('group label')->addHtml($text); } elseif (is_string($text)) { if ($translator !== NULL) { @@ -312,23 +316,23 @@ $buttons = NULL; foreach ($parent->getControls() as $control) { - if ($control->getOption('rendered') || $control instanceof Nette\Forms\Controls\HiddenField || $control->getForm(FALSE) !== $this->form) { + if ($control->getOption('rendered') || $control->getOption('type') === 'hidden' || $control->getForm(FALSE) !== $this->form) { // skip - } elseif ($control instanceof Nette\Forms\Controls\Button) { + } elseif ($control->getOption('type') === 'button') { $buttons[] = $control; } else { if ($buttons) { - $container->add($this->renderPairMulti($buttons)); + $container->addHtml($this->renderPairMulti($buttons)); $buttons = NULL; } - $container->add($this->renderPair($control)); + $container->addHtml($this->renderPair($control)); } } if ($buttons) { - $container->add($this->renderPairMulti($buttons)); + $container->addHtml($this->renderPairMulti($buttons)); } $s = ''; @@ -347,8 +351,8 @@ public function renderPair(Nette\Forms\IControl $control) { $pair = $this->getWrapper('pair container'); - $pair->add($this->renderLabel($control)); - $pair->add($this->renderControl($control)); + $pair->addHtml($this->renderLabel($control)); + $pair->addHtml($this->renderControl($control)); $pair->class($this->getValue($control->isRequired() ? 'pair .required' : 'pair .optional'), TRUE); $pair->class($control->hasErrors() ? $this->getValue('pair .error') : NULL, TRUE); $pair->class($control->getOption('class'), TRUE); @@ -367,7 +371,7 @@ */ public function renderPairMulti(array $controls) { - $s = array(); + $s = []; foreach ($controls as $control) { if (!$control instanceof Nette\Forms\IControl) { throw new Nette\InvalidArgumentException('Argument must be array of Nette\Forms\IControl instances.'); @@ -377,7 +381,10 @@ $description = ' ' . $control->getOption('description'); } elseif (is_string($description)) { - $description = ' ' . $this->getWrapper('control description')->setText($control->translate($description)); + if ($control instanceof Nette\Forms\Controls\BaseControl) { + $description = $control->translate($description); + } + $description = ' ' . $this->getWrapper('control description')->setText($description); } else { $description = ''; @@ -391,8 +398,8 @@ $s[] = $el . $description; } $pair = $this->getWrapper('pair container'); - $pair->add($this->renderLabel($control)); - $pair->add($this->getWrapper('control container')->setHtml(implode(' ', $s))); + $pair->addHtml($this->renderLabel($control)); + $pair->addHtml($this->getWrapper('control container')->setHtml(implode(' ', $s))); return $pair->render(0); } @@ -406,7 +413,7 @@ $suffix = $this->getValue('label suffix') . ($control->isRequired() ? $this->getValue('label requiredsuffix') : ''); $label = $control->getLabel(); if ($label instanceof Html) { - $label->add($suffix); + $label->addHtml($suffix); if ($control->isRequired()) { $label->class($this->getValue('control .required'), TRUE); } @@ -433,7 +440,10 @@ $description = ' ' . $description; } elseif (is_string($description)) { - $description = ' ' . $this->getWrapper('control description')->setText($control->translate($description)); + if ($control instanceof Nette\Forms\Controls\BaseControl) { + $description = $control->translate($description); + } + $description = ' ' . $this->getWrapper('control description')->setText($description); } else { $description = ''; diff -Nru php-nette-2.3.10/Nette/Forms/Rule.php php-nette-2.4-20160731/Nette/Forms/Rule.php --- php-nette-2.3.10/Nette/Forms/Rule.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Rule.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,8 +13,10 @@ /** * Single validation rule or condition represented as value object. */ -class Rule extends Nette\Object +class Rule { + use Nette\SmartObject; + /** @var IControl */ public $control; diff -Nru php-nette-2.3.10/Nette/Forms/Rules.php php-nette-2.4-20160731/Nette/Forms/Rules.php --- php-nette-2.3.10/Nette/Forms/Rules.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Rules.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,22 +13,24 @@ /** * List of validation & condition rules. */ -class Rules extends Nette\Object implements \IteratorAggregate +class Rules implements \IteratorAggregate { + use Nette\SmartObject; + /** @deprecated */ public static $defaultMessages; - /** @var Rule */ + /** @var Rule|FALSE|NULL */ private $required; /** @var Rule[] */ - private $rules = array(); + private $rules = []; /** @var Rules */ private $parent; /** @var array */ - private $toggles = array(); + private $toggles = []; /** @var IControl */ private $control; @@ -48,9 +50,9 @@ public function setRequired($value = TRUE) { if ($value) { - $this->addRule(Form::REQUIRED, is_string($value) ? $value : NULL); + $this->addRule(Form::REQUIRED, $value === TRUE ? NULL : $value); } else { - $this->required = NULL; + $this->required = FALSE; } return $this; } @@ -62,7 +64,16 @@ */ public function isRequired() { - return $this->required instanceof Rule ? !$this->required->isNegative : FALSE; + return (bool) $this->required; + } + + + /** + * @internal + */ + public function isOptional() + { + return $this->required === FALSE; } @@ -120,10 +131,10 @@ $rule = new Rule; $rule->control = $control; $rule->validator = $validator; - $this->adjustOperation($rule); $rule->arg = $arg; $rule->branch = new static($this->control); $rule->branch->parent = $this; + $this->adjustOperation($rule); $this->rules[] = $rule; return $rule->branch; @@ -200,13 +211,13 @@ * @internal * @return array */ - public function getToggleStates($toggles = array(), $success = TRUE) + public function getToggleStates($toggles = [], $success = TRUE) { foreach ($this->toggles as $id => $hide) { $toggles[$id] = ($success xor !$hide) || !empty($toggles[$id]); } - foreach ($this as $rule) { + foreach ($this->rules as $rule) { if ($rule->branch) { $toggles = $rule->branch->getToggleStates($toggles, $success && static::validateRule($rule)); } @@ -219,12 +230,16 @@ * Validates against ruleset. * @return bool */ - public function validate() + public function validate($emptyOptional = FALSE) { + $emptyOptional = $emptyOptional || $this->isOptional() && !$this->control->isFilled(); foreach ($this as $rule) { - $success = $this->validateRule($rule); + if (!$rule->branch && $emptyOptional && $rule->validator !== Form::FILLED) { + return TRUE; + } - if ($success && $rule->branch && !$rule->branch->validate()) { + $success = $this->validateRule($rule); + if ($success && $rule->branch && !$rule->branch->validate($rule->validator === Form::BLANK ? FALSE : $emptyOptional)) { return FALSE; } elseif (!$success && !$rule->branch) { @@ -237,12 +252,35 @@ /** + * @internal + */ + public function check() + { + if ($this->required !== NULL) { + return; + } + foreach ($this->rules as $rule) { + if ($rule->control === $this->control && ($rule->validator === Form::FILLED || $rule->validator === Form::BLANK)) { + // ignore + } elseif ($rule->branch) { + if ($rule->branch->check() === TRUE) { + return TRUE; + } + } else { + trigger_error("Missing setRequired(TRUE | FALSE) on field '{$rule->control->getName()}' in form '{$rule->control->getForm()->getName()}'.", E_USER_WARNING); + return TRUE; + } + } + } + + + /** * Validates single rule. * @return bool */ public static function validateRule(Rule $rule) { - $args = is_array($rule->arg) ? $rule->arg : array($rule->arg); + $args = is_array($rule->arg) ? $rule->arg : [$rule->arg]; foreach ($args as & $val) { $val = $val instanceof IControl ? $val->getValue() : $val; } @@ -275,6 +313,15 @@ if (is_string($rule->validator) && ord($rule->validator[0]) > 127) { $rule->isNegative = TRUE; $rule->validator = ~$rule->validator; + if (!$rule->branch) { + $name = strncmp($rule->validator, ':', 1) ? $rule->validator : 'Form:' . strtoupper($rule->validator); + trigger_error("Negative validation rules such as ~$name are deprecated.", E_USER_DEPRECATED); + } + if ($rule->validator === Form::FILLED) { + $rule->validator = Form::BLANK; + $rule->isNegative = FALSE; + trigger_error('Replace negative validation rule ~Form::FILLED with Form::BLANK.', E_USER_DEPRECATED); + } } if (!is_callable($this->getCallback($rule))) { diff -Nru php-nette-2.3.10/Nette/Forms/Validator.php php-nette-2.4-20160731/Nette/Forms/Validator.php --- php-nette-2.3.10/Nette/Forms/Validator.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Forms/Validator.php 2016-07-31 17:46:36.000000000 +0000 @@ -15,10 +15,12 @@ /** * Common validators. */ -class Validator extends Nette\Object +class Validator { + use Nette\StaticClass; + /** @var array */ - public static $messages = array( + public static $messages = [ Form::PROTECTION => 'Your session has expired. Please return to the home page and try again.', Form::EQUAL => 'Please enter %s.', Form::NOT_EQUAL => 'This value should not be %s.', @@ -39,7 +41,8 @@ Form::MIME_TYPE => 'The uploaded file is not in the expected format.', Form::IMAGE => 'The uploaded file must be image in format JPEG, GIF or PNG.', Controls\SelectBox::VALID => 'Please select a valid option.', - ); + Controls\UploadControl::VALID => 'An error occurred during file upload.', + ]; /** @internal */ @@ -64,10 +67,10 @@ static $i = -1; switch ($m[1]) { case 'name': return $rule->control->getName(); - case 'label': return $rule->control->translate($rule->control->caption); + case 'label': return $rule->control instanceof Controls\BaseControl ? $rule->control->translate($rule->control->caption) : NULL; case 'value': return $withValue ? $rule->control->getValue() : $m[0]; default: - $args = is_array($rule->arg) ? $rule->arg : array($rule->arg); + $args = is_array($rule->arg) ? $rule->arg : [$rule->arg]; $i = (int) $m[1] ? $m[1] - 1 : $i + 1; return isset($args[$i]) ? ($args[$i] instanceof IControl ? ($withValue ? $args[$i]->getValue() : "%$i") : $args[$i]) : ''; } @@ -86,8 +89,8 @@ public static function validateEqual(IControl $control, $arg) { $value = $control->getValue(); - foreach ((is_array($value) ? $value : array($value)) as $val) { - foreach ((is_array($arg) ? $arg : array($arg)) as $item) { + foreach ((is_array($value) ? $value : [$value]) as $val) { + foreach ((is_array($arg) ? $arg : [$arg]) as $item) { if ((string) $val === (string) $item) { continue 2; } @@ -132,7 +135,7 @@ * Is control valid? * @return bool */ - public static function validateValid(IControl $control) + public static function validateValid(Controls\BaseControl $control) { return $control->getRules()->validate(); } @@ -154,7 +157,7 @@ */ public static function validateMin(IControl $control, $minimum) { - return Validators::isInRange($control->getValue(), array($minimum, NULL)); + return Validators::isInRange($control->getValue(), [$minimum, NULL]); } @@ -164,7 +167,7 @@ */ public static function validateMax(IControl $control, $maximum) { - return Validators::isInRange($control->getValue(), array(NULL, $maximum)); + return Validators::isInRange($control->getValue(), [NULL, $maximum]); } @@ -175,7 +178,7 @@ public static function validateLength(IControl $control, $range) { if (!is_array($range)) { - $range = array($range, $range); + $range = [$range, $range]; } $value = $control->getValue(); return Validators::isInRange(is_array($value) ? count($value) : Strings::length($value), $range); @@ -188,7 +191,7 @@ */ public static function validateMinLength(IControl $control, $length) { - return static::validateLength($control, array($length, NULL)); + return static::validateLength($control, [$length, NULL]); } @@ -198,7 +201,7 @@ */ public static function validateMaxLength(IControl $control, $length) { - return static::validateLength($control, array(NULL, $length)); + return static::validateLength($control, [NULL, $length]); } @@ -271,7 +274,7 @@ */ public static function validateFloat(IControl $control) { - $value = str_replace(array(' ', ','), array('', '.'), $control->getValue()); + $value = str_replace([' ', ','], ['', '.'], $control->getValue()); if (Validators::isNumeric($value)) { $control->setValue((float) $value); return TRUE; @@ -332,7 +335,7 @@ */ private static function toArray($value) { - return $value instanceof Nette\Http\FileUpload ? array($value) : (array) $value; + return $value instanceof Nette\Http\FileUpload ? [$value] : (array) $value; } } diff -Nru php-nette-2.3.10/Nette/Framework.php php-nette-2.4-20160731/Nette/Framework.php --- php-nette-2.3.10/Nette/Framework.php 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Framework.php 2016-07-31 17:46:18.000000000 +0000 @@ -15,8 +15,8 @@ { const NAME = 'Nette Framework', - VERSION = '2.3.10', - VERSION_ID = 20310, - REVISION = 'released on 2016-04-13'; + VERSION = '2.4', + VERSION_ID = 20400, + REVISION = ''; } diff -Nru php-nette-2.3.10/Nette/Http/Context.php php-nette-2.4-20160731/Nette/Http/Context.php --- php-nette-2.3.10/Nette/Http/Context.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/Context.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,8 +13,10 @@ /** * HTTP-specific tasks. */ -class Context extends Nette\Object +class Context { + use Nette\SmartObject; + /** @var IRequest */ private $request; @@ -31,7 +33,7 @@ /** * Attempts to cache the sent entity by its last modification date. - * @param string|int|\DateTime last modified time + * @param string|int|\DateTimeInterface last modified time * @param string strong entity tag validator * @return bool */ diff -Nru php-nette-2.3.10/Nette/Http/FileUpload.php php-nette-2.4-20160731/Nette/Http/FileUpload.php --- php-nette-2.3.10/Nette/Http/FileUpload.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/FileUpload.php 2016-07-31 17:46:36.000000000 +0000 @@ -22,8 +22,10 @@ * @property-read bool $ok * @property-read string|NULL $contents */ -class FileUpload extends Nette\Object +class FileUpload { + use Nette\SmartObject; + /** @var string */ private $name; @@ -42,7 +44,7 @@ public function __construct($value) { - foreach (array('name', 'type', 'size', 'tmp_name', 'error') as $key) { + foreach (['name', 'type', 'size', 'tmp_name', 'error'] as $key) { if (!isset($value[$key]) || !is_scalar($value[$key])) { $this->error = UPLOAD_ERR_NO_FILE; return; // or throw exception? @@ -145,11 +147,19 @@ */ public function move($dest) { - @mkdir(dirname($dest), 0777, TRUE); // @ - dir may already exist - @unlink($dest); // @ - file may not exists - if (!call_user_func(is_uploaded_file($this->tmpName) ? 'move_uploaded_file' : 'rename', $this->tmpName, $dest)) { - throw new Nette\InvalidStateException("Unable to move uploaded file '$this->tmpName' to '$dest'."); + $dir = dirname($dest); + @mkdir($dir, 0777, TRUE); // @ - dir may already exist + if (!is_dir($dir)) { + throw new Nette\InvalidStateException("Directory '$dir' cannot be created. " . error_get_last()['message']); } + @unlink($dest); // @ - file may not exists + Nette\Utils\Callback::invokeSafe( + is_uploaded_file($this->tmpName) ? 'move_uploaded_file' : 'rename', + [$this->tmpName, $dest], + function ($message) { + throw new Nette\InvalidStateException("Unable to move uploaded file '$this->tmpName' to '$dest'. $message"); + } + ); @chmod($dest, 0666); // @ - possible low permission to chmod $this->tmpName = $dest; return $this; @@ -162,7 +172,7 @@ */ public function isImage() { - return in_array($this->getContentType(), array('image/gif', 'image/png', 'image/jpeg'), TRUE); + return in_array($this->getContentType(), ['image/gif', 'image/png', 'image/jpeg'], TRUE); } diff -Nru php-nette-2.3.10/Nette/Http/Helpers.php php-nette-2.4-20160731/Nette/Http/Helpers.php --- php-nette-2.3.10/Nette/Http/Helpers.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/Helpers.php 2016-07-31 17:46:36.000000000 +0000 @@ -16,10 +16,11 @@ */ class Helpers { + use Nette\StaticClass; /** * Returns HTTP valid date format. - * @param string|int|\DateTime + * @param string|int|\DateTimeInterface * @return string */ public static function formatDate($time) @@ -41,10 +42,10 @@ $ip = implode('', array_map($tmp, unpack('N*', inet_pton($ip)))); $mask = implode('', array_map($tmp, unpack('N*', inet_pton($mask)))); $max = strlen($ip); - if (!$max || $max !== strlen($mask) || $size < 0 || $size > $max) { + if (!$max || $max !== strlen($mask) || (int) $size < 0 || (int) $size > $max) { return FALSE; } - return strncmp($ip, $mask, $size === '' ? $max : $size) === 0; + return strncmp($ip, $mask, $size === '' ? $max : (int) $size) === 0; } @@ -59,7 +60,7 @@ return; } - $flatten = array(); + $flatten = []; foreach (headers_list() as $header) { if (preg_match('#^Set-Cookie: .+?=#', $header, $m)) { $flatten[$m[0]] = $header; @@ -71,19 +72,4 @@ } } - - /** - * @internal - */ - public static function stripSlashes($arr, $onlyKeys = FALSE) - { - $res = array(); - foreach ($arr as $k => $v) { - $res[stripslashes($k)] = is_array($v) - ? self::stripSlashes($v, $onlyKeys) - : ($onlyKeys ? $v : stripslashes($v)); - } - return $res; - } - } diff -Nru php-nette-2.3.10/Nette/Http/IRequest.php php-nette-2.4-20160731/Nette/Http/IRequest.php --- php-nette-2.3.10/Nette/Http/IRequest.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/IRequest.php 2016-07-31 17:46:36.000000000 +0000 @@ -20,7 +20,8 @@ HEAD = 'HEAD', PUT = 'PUT', DELETE = 'DELETE', - PATCH = 'PATCH'; + PATCH = 'PATCH', + OPTIONS = 'OPTIONS'; /** * Returns URL object. diff -Nru php-nette-2.3.10/Nette/Http/IResponse.php php-nette-2.4-20160731/Nette/Http/IResponse.php --- php-nette-2.3.10/Nette/Http/IResponse.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/IResponse.php 2016-07-31 17:46:36.000000000 +0000 @@ -111,7 +111,7 @@ /** * Sets the number of seconds before a page cached on a browser expires. - * @param string|int|\DateTime time, value 0 means "until the browser is closed" + * @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed" * @return void */ function setExpiration($seconds); diff -Nru php-nette-2.3.10/Nette/Http/RequestFactory.php php-nette-2.4-20160731/Nette/Http/RequestFactory.php --- php-nette-2.3.10/Nette/Http/RequestFactory.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/RequestFactory.php 2016-07-31 17:46:36.000000000 +0000 @@ -14,22 +14,24 @@ /** * Current HTTP request factory. */ -class RequestFactory extends Nette\Object +class RequestFactory { + use Nette\SmartObject; + /** @internal */ const CHARS = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; /** @var array */ - public $urlFilters = array( - 'path' => array('#/{2,}#' => '/'), // '%20' => '' - 'url' => array(), // '#[.,)]\z#' => '' - ); + public $urlFilters = [ + 'path' => ['#/{2,}#' => '/'], // '%20' => '' + 'url' => [], // '#[.,)]\z#' => '' + ]; /** @var bool */ private $binary = FALSE; /** @var array */ - private $proxies = array(); + private $proxies = []; /** @@ -72,14 +74,15 @@ ) { $url->setHost(strtolower($pair[1])); if (isset($pair[2])) { - $url->setPort(substr($pair[2], 1)); + $url->setPort((int) substr($pair[2], 1)); } elseif (isset($_SERVER['SERVER_PORT'])) { - $url->setPort($_SERVER['SERVER_PORT']); + $url->setPort((int) $_SERVER['SERVER_PORT']); } } // path & query $requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; + $requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl); $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); $tmp = explode('?', $requestUrl, 2); $path = Url::unescape($tmp[0], '%/?#'); @@ -98,21 +101,16 @@ $url->setScriptPath($path); // GET, POST, COOKIE - $useFilter = (!in_array(ini_get('filter.default'), array('', 'unsafe_raw')) || ini_get('filter.default_flags')); + $useFilter = (!in_array(ini_get('filter.default'), ['', 'unsafe_raw']) || ini_get('filter.default_flags')); $query = $url->getQueryParameters(); - $post = $useFilter ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) : (empty($_POST) ? array() : $_POST); - $cookies = $useFilter ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) : (empty($_COOKIE) ? array() : $_COOKIE); - - if (get_magic_quotes_gpc()) { - $post = Helpers::stripslashes($post, $useFilter); - $cookies = Helpers::stripslashes($cookies, $useFilter); - } + $post = $useFilter ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) : (empty($_POST) ? [] : $_POST); + $cookies = $useFilter ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) : (empty($_COOKIE) ? [] : $_COOKIE); // remove invalid characters $reChars = '#^[' . self::CHARS . ']*+\z#u'; if (!$this->binary) { - $list = array(& $query, & $post, & $cookies); + $list = [& $query, & $post, & $cookies]; while (list($key, $val) = each($list)) { foreach ($val as $k => $v) { if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { @@ -133,8 +131,8 @@ // FILES and create FileUpload objects - $files = array(); - $list = array(); + $files = []; + $list = []; if (!empty($_FILES)) { foreach ($_FILES as $k => $v) { if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { @@ -150,9 +148,6 @@ continue; } elseif (!is_array($v['name'])) { - if (get_magic_quotes_gpc()) { - $v['name'] = stripSlashes($v['name']); - } if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) { $v['name'] = ''; } @@ -166,14 +161,14 @@ if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { continue; } - $list[] = array( + $list[] = [ 'name' => $v['name'][$k], 'type' => $v['type'][$k], 'size' => $v['size'][$k], 'tmp_name' => $v['tmp_name'][$k], 'error' => $v['error'][$k], '@' => & $v['@'][$k], - ); + ]; } } @@ -182,7 +177,7 @@ if (function_exists('apache_request_headers')) { $headers = apache_request_headers(); } else { - $headers = array(); + $headers = []; foreach ($_SERVER as $k => $v) { if (strncmp($k, 'HTTP_', 5) == 0) { $k = substr($k, 5); @@ -193,33 +188,80 @@ } } + $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL; + $remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL; - $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL; - $remoteHost = isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL; + // use real client address and host if trusted proxy is used + $usingTrustedProxy = $remoteAddr && array_filter($this->proxies, function ($proxy) use ($remoteAddr) { + return Helpers::ipMatch($remoteAddr, $proxy); + }); + if ($usingTrustedProxy) { + if (!empty($_SERVER['HTTP_FORWARDED'])) { + $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); + foreach ($forwardParams as $forwardParam) { + list($key, $value) = explode('=', $forwardParam, 2) + [1 => NULL]; + $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); + } - // proxy - foreach ($this->proxies as $proxy) { - if (Helpers::ipMatch($remoteAddr, $proxy) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $proxies = $this->proxies; - $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) use ($proxies) { - return !array_filter($proxies, function ($proxy) use ($ip) { - return Helpers::ipMatch(trim($ip), $proxy); - }); - }); - $remoteAddr = trim(end($xForwardedForWithoutProxies)); + if (isset($proxyParams['for'])) { + $address = $proxyParams['for'][0]; + if (strpos($address, '[') === FALSE) { //IPv4 + $remoteAddr = explode(':', $address)[0]; + } else { //IPv6 + $remoteAddr = substr($address, 1, strpos($address, ']') - 1); + } + } + + if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { + $host = $proxyParams['host'][0]; + $startingDelimiterPosition = strpos($host, '['); + if ($startingDelimiterPosition === FALSE) { //IPv4 + $remoteHostArr = explode(':', $host); + $remoteHost = $remoteHostArr[0]; + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } else { //IPv6 + $endingDelimiterPosition = strpos($host, ']'); + $remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1); + $remoteHostArr = explode(':', substr($host, $endingDelimiterPosition)); + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } + } - if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + $scheme = (isset($proxyParams['scheme']) && count($proxyParams['scheme']) === 1) ? $proxyParams['scheme'][0] : 'http'; + $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); + } else { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http'); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) { + return !array_filter($this->proxies, function ($proxy) use ($ip) { + return Helpers::ipMatch(trim($ip), $proxy); + }); + }); + $remoteAddr = trim(end($xForwardedForWithoutProxies)); $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); + } + + if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); if (isset($xForwardedHost[$xForwardedForRealIpKey])) { $remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]); } } - break; } } - + // method, eg. GET, PUT, ... $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : NULL; if ($method === 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) && preg_match('#^[A-Z]+\z#', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) @@ -229,16 +271,7 @@ // raw body $rawBodyCallback = function () { - static $rawBody; - - if (PHP_VERSION_ID >= 50600) { - return file_get_contents('php://input'); - - } elseif ($rawBody === NULL) { // can be read only once in PHP < 5.6 - $rawBody = (string) file_get_contents('php://input'); - } - - return $rawBody; + return file_get_contents('php://input'); }; return new Request($url, NULL, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback); diff -Nru php-nette-2.3.10/Nette/Http/Request.php php-nette-2.4-20160731/Nette/Http/Request.php --- php-nette-2.3.10/Nette/Http/Request.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/Request.php 2016-07-31 17:46:36.000000000 +0000 @@ -27,8 +27,10 @@ * @property-read string|NULL $remoteHost * @property-read string|NULL $rawBody */ -class Request extends Nette\Object implements IRequest +class Request implements IRequest { + use Nette\SmartObject; + /** @var string */ private $method; @@ -134,11 +136,6 @@ */ public function getFile($key) { - if (func_num_args() > 1) { - trigger_error('Calling getFile() with multiple keys is deprecated.', E_USER_DEPRECATED); - return Nette\Utils\Arrays::get($this->files, func_get_args(), NULL); - } - return isset($this->files[$key]) ? $this->files[$key] : NULL; } @@ -204,6 +201,7 @@ */ public function isPost() { + trigger_error('Method isPost() is deprecated, use isMethod(\'POST\') instead.', E_USER_DEPRECATED); return $this->isMethod('POST'); } diff -Nru php-nette-2.3.10/Nette/Http/Response.php php-nette-2.4-20160731/Nette/Http/Response.php --- php-nette-2.3.10/Nette/Http/Response.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/Response.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,9 +13,13 @@ /** * HttpResponse class. + * + * @property-read array $headers */ -class Response extends Nette\Object implements IResponse +class Response implements IResponse { + use Nette\SmartObject; + /** @var bool Send invisible garbage for IE 6? */ private static $fixIE = TRUE; @@ -25,10 +29,10 @@ /** @var string The path in which the cookie will be available */ public $cookiePath = '/'; - /** @var string Whether the cookie is available only through HTTPS */ + /** @var bool Whether the cookie is available only through HTTPS */ public $cookieSecure = FALSE; - /** @var string Whether the cookie is hidden from client-side */ + /** @var bool Whether the cookie is hidden from client-side */ public $cookieHttpOnly = TRUE; /** @var bool Whether warn on possible problem with data in output buffer */ @@ -40,16 +44,10 @@ public function __construct() { - if (PHP_VERSION_ID >= 50400) { - if (is_int($code = http_response_code())) { - $this->code = $code; - } + if (is_int($code = http_response_code())) { + $this->code = $code; } - if (PHP_VERSION_ID >= 50401) { // PHP bug #61106 - $rm = new \ReflectionMethod('Nette\Http\Helpers::removeDuplicateCookies'); - header_register_callback($rm->getClosure()); // requires closure due PHP bug #66375 - } } @@ -154,12 +152,13 @@ /** * Sets the number of seconds before a page cached on a browser expires. - * @param string|int|\DateTime time, value 0 means "until the browser is closed" + * @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed" * @return self * @throws Nette\InvalidStateException if HTTP headers have been sent */ public function setExpiration($time) { + $this->setHeader('Pragma', NULL); if (!$time) { // no cache $this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate'); $this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT'); @@ -208,7 +207,7 @@ */ public function getHeaders() { - $headers = array(); + $headers = []; foreach (headers_list() as $header) { $a = strpos($header, ':'); $headers[substr($header, 0, $a)] = (string) substr($header, $a + 2); @@ -222,6 +221,7 @@ */ public static function date($time = NULL) { + trigger_error('Method date() is deprecated, use Nette\Http\Helpers::formatDate() instead.', E_USER_DEPRECATED); return Helpers::formatDate($time); } @@ -232,7 +232,7 @@ public function __destruct() { if (self::$fixIE && isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE - && in_array($this->code, array(400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505), TRUE) + && in_array($this->code, [400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505], TRUE) && preg_match('#^text/html(?:;|$)#', $this->getHeader('Content-Type', 'text/html')) ) { echo Nette\Utils\Random::generate(2e3, " \t\r\n"); // sends invisible garbage for IE @@ -245,7 +245,7 @@ * Sends a cookie. * @param string name of the cookie * @param string value - * @param string|int|\DateTime expiration time, value 0 means "until the browser is closed" + * @param string|int|\DateTimeInterface expiration time, value 0 means "until the browser is closed" * @param string * @param string * @param bool @@ -259,7 +259,7 @@ setcookie( $name, $value, - $time ? DateTime::from($time)->format('U') : 0, + $time ? (int) DateTime::from($time)->format('U') : 0, $path === NULL ? $this->cookiePath : (string) $path, $domain === NULL ? $this->cookieDomain : (string) $domain, $secure === NULL ? $this->cookieSecure : (bool) $secure, @@ -285,20 +285,15 @@ } - /** @internal @deprecated */ - public function removeDuplicateCookies() - { - trigger_error('Use Nette\Http\Helpers::removeDuplicateCookies()', E_USER_WARNING); - } - - private function checkHeaders() { - if (headers_sent($file, $line)) { + if (PHP_SAPI === 'cli') { + + } elseif (headers_sent($file, $line)) { throw new Nette\InvalidStateException('Cannot send header after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.')); } elseif ($this->warnOnBuffer && ob_get_length() && !array_filter(ob_get_status(TRUE), function ($i) { return !$i['chunk_size']; })) { - trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or start session earlier.', E_USER_NOTICE); + trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or start session earlier.'); } } diff -Nru php-nette-2.3.10/Nette/Http/Session.php php-nette-2.4-20160731/Nette/Http/Session.php --- php-nette-2.3.10/Nette/Http/Session.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/Session.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,19 +13,21 @@ /** * Provides access to session sections as well as session settings and management methods. */ -class Session extends Nette\Object +class Session { - /** Default file lifetime is 3 hours */ - const DEFAULT_FILE_LIFETIME = 10800; + use Nette\SmartObject; + + /** Default file lifetime */ + const DEFAULT_FILE_LIFETIME = 3 * Nette\Utils\DateTime::HOUR; /** @var bool has been session ID regenerated? */ - private $regenerated; + private $regenerated = FALSE; /** @var bool has been session started? */ - private static $started; + private static $started = FALSE; /** @var array default configuration */ - private $options = array( + private $options = [ // security 'referer_check' => '', // must be disabled because PHP implementation is invalid 'use_cookies' => 1, // must be enabled to prevent Session Hijacking and Fixation @@ -45,7 +47,7 @@ 'cache_expire' => NULL, // (default "180") 'hash_function' => NULL, // (default "0", means MD5) 'hash_bits_per_character' => NULL, // (default "4") - ); + ]; /** @var IRequest */ private $request; @@ -86,13 +88,12 @@ try { // session_start returns FALSE on failure only sometimes - Nette\Utils\Callback::invokeSafe('session_start', array(), function ($message) use (& $e) { + Nette\Utils\Callback::invokeSafe('session_start', [], function ($message) use (& $e) { $e = new Nette\InvalidStateException($message); }); } catch (\Exception $e) { } - Helpers::removeDuplicateCookies(); if ($e) { @session_write_close(); // this is needed throw $e; @@ -148,7 +149,7 @@ $this->regenerateId(); } - register_shutdown_function(array($this, 'clean')); + register_shutdown_function([$this, 'clean']); } @@ -217,14 +218,13 @@ if (headers_sent($file, $line)) { throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.')); } - if (session_id() !== '') { + if (session_status() === PHP_SESSION_ACTIVE) { session_regenerate_id(TRUE); + session_write_close(); } - session_write_close(); $backup = $_SESSION; session_start(); $_SESSION = $backup; - Helpers::removeDuplicateCookies(); } $this->regenerated = TRUE; } @@ -252,9 +252,9 @@ } session_name($name); - return $this->setOptions(array( + return $this->setOptions([ 'name' => $name, - )); + ]); } @@ -278,7 +278,7 @@ * @return SessionSection * @throws Nette\InvalidArgumentException */ - public function getSection($section, $class = 'Nette\Http\SessionSection') + public function getSection($section, $class = SessionSection::class) { return new $class($this, $section); } @@ -388,7 +388,7 @@ */ private function configure(array $config) { - $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1); + $special = ['cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1]; foreach ($config as $key => $value) { if (!strncmp($key, 'session.', 8)) { // back compatibility @@ -406,7 +406,7 @@ $cookie[substr($key, 7)] = $value; } else { - if (defined('SID')) { + if (session_status() === PHP_SESSION_ACTIVE) { throw new Nette\InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started" . ($this->started ? '.' : ' by session.auto_start or session_start().')); } if (isset($special[$key])) { @@ -414,9 +414,9 @@ $key($value); } elseif (function_exists('ini_set')) { - ini_set("session.$key", $value); + ini_set("session.$key", (string) $value); - } elseif (ini_get("session.$key") != $value) { // intentionally == + } elseif (ini_get("session.$key") != $value) { // intentionally != throw new Nette\NotSupportedException("Unable to set 'session.$key' to '$value' because function ini_set() is disabled."); } } @@ -440,23 +440,23 @@ /** * Sets the amount of time allowed between requests before the session will be terminated. - * @param string|int|\DateTime time, value 0 means "until the browser is closed" + * @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed" * @return self */ public function setExpiration($time) { if (empty($time)) { - return $this->setOptions(array( + return $this->setOptions([ 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME, 'cookie_lifetime' => 0, - )); + ]); } else { $time = Nette\Utils\DateTime::from($time)->format('U') - time(); - return $this->setOptions(array( + return $this->setOptions([ 'gc_maxlifetime' => $time, 'cookie_lifetime' => $time, - )); + ]); } } @@ -470,11 +470,11 @@ */ public function setCookieParameters($path, $domain = NULL, $secure = NULL) { - return $this->setOptions(array( + return $this->setOptions([ 'cookie_path' => $path, 'cookie_domain' => $domain, 'cookie_secure' => $secure, - )); + ]); } @@ -494,14 +494,14 @@ */ public function setSavePath($path) { - return $this->setOptions(array( + return $this->setOptions([ 'save_path' => $path, - )); + ]); } /** - * Sets user session storage for PHP < 5.4. For PHP >= 5.4, use setHandler(). + * @deprecated use setHandler(). * @return self */ public function setStorage(ISessionStorage $storage) @@ -510,8 +510,8 @@ throw new Nette\InvalidStateException('Unable to set storage when session has been started.'); } session_set_save_handler( - array($storage, 'open'), array($storage, 'close'), array($storage, 'read'), - array($storage, 'write'), array($storage, 'remove'), array($storage, 'clean') + [$storage, 'open'], [$storage, 'close'], [$storage, 'read'], + [$storage, 'write'], [$storage, 'remove'], [$storage, 'clean'] ); return $this; } @@ -537,10 +537,6 @@ */ private function sendCookie() { - if (!headers_sent() && ob_get_level() && ob_get_length()) { - trigger_error('Possible problem: you are starting session while already having some data in output buffer. This may not work if the outputted data grows. Try starting the session earlier.', E_USER_NOTICE); - } - $cookie = $this->getCookieParameters(); $this->response->setCookie( session_name(), session_id(), diff -Nru php-nette-2.3.10/Nette/Http/SessionSection.php php-nette-2.4-20160731/Nette/Http/SessionSection.php --- php-nette-2.3.10/Nette/Http/SessionSection.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/SessionSection.php 2016-07-31 17:46:36.000000000 +0000 @@ -13,8 +13,10 @@ /** * Session section. */ -class SessionSection extends Nette\Object implements \IteratorAggregate, \ArrayAccess +class SessionSection implements \IteratorAggregate, \ArrayAccess { + use Nette\SmartObject; + /** @var Session */ private $session; @@ -95,7 +97,7 @@ { $this->start(); if ($this->warnOnUndefined && !array_key_exists($name, $this->data)) { - trigger_error("The variable '$name' does not exist in session section", E_USER_NOTICE); + trigger_error("The variable '$name' does not exist in session section"); } return $this->data[$name]; @@ -175,7 +177,7 @@ /** * Sets the expiration of the section or specific variables. - * @param string|int|\DateTime time, value 0 means "until the browser is closed" + * @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed" * @param mixed optional list of variables / single variable to expire * @return self */ @@ -189,12 +191,12 @@ $time = Nette\Utils\DateTime::from($time)->format('U'); $max = (int) ini_get('session.gc_maxlifetime'); if ($max !== 0 && ($time - time() > $max + 3)) { // 0 - unlimited in memcache handler, 3 - bulgarian constant - trigger_error("The expiration time is greater than the session expiration $max seconds", E_USER_NOTICE); + trigger_error("The expiration time is greater than the session expiration $max seconds"); } $whenBrowserIsClosed = FALSE; } - foreach (is_array($variables) ? $variables : array($variables) as $variable) { + foreach (is_array($variables) ? $variables : [$variables] as $variable) { $this->meta[$variable]['T'] = $time; $this->meta[$variable]['B'] = $whenBrowserIsClosed; } @@ -210,7 +212,7 @@ public function removeExpiration($variables = NULL) { $this->start(); - foreach (is_array($variables) ? $variables : array($variables) as $variable) { + foreach (is_array($variables) ? $variables : [$variables] as $variable) { unset($this->meta['']['T'], $this->meta['']['B']); } } diff -Nru php-nette-2.3.10/Nette/Http/Url.php php-nette-2.4-20160731/Nette/Http/Url.php --- php-nette-2.3.10/Nette/Http/Url.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/Url.php 2016-07-31 17:46:36.000000000 +0000 @@ -45,16 +45,18 @@ * @property-read string $relativeUrl * @property-read array $queryParameters */ -class Url extends Nette\Object +class Url implements \JsonSerializable { + use Nette\SmartObject; + /** @var array */ - public static $defaultPorts = array( + public static $defaultPorts = [ 'http' => 80, 'https' => 443, 'ftp' => 21, 'news' => 119, 'nntp' => 119, - ); + ]; /** @var string */ private $scheme = ''; @@ -68,14 +70,14 @@ /** @var string */ private $host = ''; - /** @var int */ + /** @var int|NULL */ private $port; /** @var string */ private $path = ''; /** @var array */ - private $query = array(); + private $query = []; /** @var string */ private $fragment = ''; @@ -99,7 +101,7 @@ $this->user = isset($p['user']) ? rawurldecode($p['user']) : ''; $this->password = isset($p['pass']) ? rawurldecode($p['pass']) : ''; $this->setPath(isset($p['path']) ? $p['path'] : ''); - $this->setQuery(isset($p['query']) ? $p['query'] : array()); + $this->setQuery(isset($p['query']) ? $p['query'] : []); $this->fragment = isset($p['fragment']) ? rawurldecode($p['fragment']) : ''; } elseif ($url instanceof self) { @@ -213,7 +215,7 @@ /** * Returns the port part of URI. - * @return int + * @return int|NULL */ public function getPort() { @@ -280,9 +282,6 @@ */ public function getQuery() { - if (PHP_VERSION_ID < 50400) { - return str_replace('+', '%20', http_build_query($this->query, '', '&')); - } return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986); } @@ -424,7 +423,7 @@ ksort($query); $query2 = $this->query; ksort($query2); - $http = in_array($this->scheme, array('http', 'https'), TRUE); + $http = in_array($this->scheme, ['http', 'https'], TRUE); return $url->scheme === $this->scheme && !strcasecmp($url->host, $this->host) && $url->getPort() === $this->getPort() @@ -462,6 +461,15 @@ /** + * @return string + */ + public function jsonSerialize() + { + return $this->getAbsoluteUrl(); + } + + + /** * Similar to rawurldecode, but preserves reserved chars encoded. * @param string to decode * @param string reserved characters @@ -490,9 +498,6 @@ public static function parseQuery($s) { parse_str($s, $res); - if (get_magic_quotes_gpc()) { // for PHP 5.3 - $res = Helpers::stripSlashes($res); - } return $res; } diff -Nru php-nette-2.3.10/Nette/Http/UserStorage.php php-nette-2.4-20160731/Nette/Http/UserStorage.php --- php-nette-2.3.10/Nette/Http/UserStorage.php 2016-04-13 18:50:42.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Http/UserStorage.php 2016-07-31 17:46:36.000000000 +0000 @@ -14,8 +14,10 @@ /** * Session storage for user object. */ -class UserStorage extends Nette\Object implements Nette\Security\IUserStorage +class UserStorage implements Nette\Security\IUserStorage { + use Nette\SmartObject; + /** @var string */ private $namespace = ''; @@ -117,7 +119,7 @@ /** * Enables log out after inactivity. - * @param string|int|\DateTime Number of seconds or timestamp + * @param string|int|\DateTimeInterface Number of seconds or timestamp * @param int Log out when the browser is closed | Clear the identity from persistent storage? * @return self */ diff -Nru php-nette-2.3.10/Nette/Iterators/CachingIterator.php php-nette-2.4-20160731/Nette/Iterators/CachingIterator.php --- php-nette-2.3.10/Nette/Iterators/CachingIterator.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Iterators/CachingIterator.php 2016-07-31 17:46:44.000000000 +0000 @@ -8,7 +8,6 @@ namespace Nette\Iterators; use Nette; -use Nette\Utils\ObjectMixin; /** @@ -25,6 +24,8 @@ */ class CachingIterator extends \CachingIterator implements \Countable { + use Nette\SmartObject; + /** @var int */ private $counter = 0; @@ -172,69 +173,4 @@ return $this->getInnerIterator()->current(); } - - /********************* Nette\Object behaviour ****************d*g**/ - - - /** - * Call to undefined method. - * @param string method name - * @param array arguments - * @return mixed - * @throws Nette\MemberAccessException - */ - public function __call($name, $args) - { - return ObjectMixin::call($this, $name, $args); - } - - - /** - * Returns property value. Do not call directly. - * @param string property name - * @return mixed property value - * @throws Nette\MemberAccessException if the property is not defined. - */ - public function &__get($name) - { - return ObjectMixin::get($this, $name); - } - - - /** - * Sets value of a property. Do not call directly. - * @param string property name - * @param mixed property value - * @return void - * @throws Nette\MemberAccessException if the property is not defined or is read-only - */ - public function __set($name, $value) - { - ObjectMixin::set($this, $name, $value); - } - - - /** - * Is property defined? - * @param string property name - * @return bool - */ - public function __isset($name) - { - return ObjectMixin::has($this, $name); - } - - - /** - * Access to undeclared property. - * @param string property name - * @return void - * @throws Nette\MemberAccessException - */ - public function __unset($name) - { - ObjectMixin::remove($this, $name); - } - - } diff -Nru php-nette-2.3.10/Nette/Iterators/Filter.php php-nette-2.4-20160731/Nette/Iterators/Filter.php --- php-nette-2.3.10/Nette/Iterators/Filter.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Iterators/Filter.php 2016-07-31 17:46:44.000000000 +0000 @@ -12,6 +12,7 @@ /** * CallbackFilterIterator for PHP < 5.4. + * @deprecated use CallbackFilterIterator */ class Filter extends \FilterIterator { @@ -21,6 +22,7 @@ public function __construct(\Iterator $iterator, $callback) { + trigger_error(__CLASS__ . ' is deprecated, use CallbackFilterIterator.', E_USER_WARNING); parent::__construct($iterator); $this->callback = Nette\Utils\Callback::check($callback); } diff -Nru php-nette-2.3.10/Nette/Iterators/Mapper.php php-nette-2.4-20160731/Nette/Iterators/Mapper.php --- php-nette-2.3.10/Nette/Iterators/Mapper.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Iterators/Mapper.php 2016-07-31 17:46:44.000000000 +0000 @@ -19,10 +19,10 @@ private $callback; - public function __construct(\Traversable $iterator, $callback) + public function __construct(\Traversable $iterator, callable $callback) { parent::__construct($iterator); - $this->callback = Nette\Utils\Callback::check($callback); + $this->callback = $callback; } diff -Nru php-nette-2.3.10/Nette/Iterators/RecursiveFilter.php php-nette-2.4-20160731/Nette/Iterators/RecursiveFilter.php --- php-nette-2.3.10/Nette/Iterators/RecursiveFilter.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Iterators/RecursiveFilter.php 2016-07-31 17:46:44.000000000 +0000 @@ -10,12 +10,14 @@ /** * RecursiveCallbackFilterIterator for PHP < 5.4. + * @deprecated use RecursiveCallbackFilterIterator */ class RecursiveFilter extends Filter implements \RecursiveIterator { public function __construct(\RecursiveIterator $iterator, $callback) { + trigger_error(__CLASS__ . ' is deprecated, use RecursiveCallbackFilterIterator.', E_USER_WARNING); parent::__construct($iterator, $callback); } diff -Nru php-nette-2.3.10/Nette/Latte/Compiler/Compiler.php php-nette-2.4-20160731/Nette/Latte/Compiler/Compiler.php --- php-nette-2.3.10/Nette/Latte/Compiler/Compiler.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler/Compiler.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,799 @@ + IMacro[]] */ + private $macros; + + /** @var int[] IMacro flags */ + private $flags; + + /** @var HtmlNode */ + private $htmlNode; + + /** @var MacroNode */ + private $macroNode; + + /** @var string[] */ + private $placeholders = []; + + /** @var string */ + private $contentType = self::CONTENT_HTML; + + /** @var string|NULL */ + private $context; + + /** @var mixed */ + private $lastAttrValue; + + /** @var int */ + private $tagOffset; + + /** @var bool */ + private $inHead; + + /** @var array of [name => [body, arguments]] */ + private $methods = []; + + /** @var array of [name => serialized value] */ + private $properties = []; + + /** Context-aware escaping content types */ + const + CONTENT_HTML = Engine::CONTENT_HTML, + CONTENT_XHTML = Engine::CONTENT_XHTML, + CONTENT_XML = Engine::CONTENT_XML, + CONTENT_JS = Engine::CONTENT_JS, + CONTENT_CSS = Engine::CONTENT_CSS, + CONTENT_ICAL = Engine::CONTENT_ICAL, + CONTENT_TEXT = Engine::CONTENT_TEXT; + + /** @internal Context-aware escaping HTML contexts */ + const + CONTEXT_HTML_TEXT = NULL, + CONTEXT_HTML_TAG = 'Tag', + CONTEXT_HTML_ATTRIBUTE = 'Attr', + CONTEXT_HTML_ATTRIBUTE_JS = 'AttrJs', + CONTEXT_HTML_ATTRIBUTE_CSS = 'AttrCss', + CONTEXT_HTML_ATTRIBUTE_URL = 'AttrUrl', + CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL = 'AttrUnquotedUrl', + CONTEXT_HTML_COMMENT = 'Comment', + CONTEXT_HTML_BOGUS_COMMENT = 'Bogus', + CONTEXT_HTML_CSS = 'Css', + CONTEXT_HTML_JS = 'Js', + + CONTEXT_XML_TEXT = self::CONTEXT_HTML_TEXT, + CONTEXT_XML_TAG = self::CONTEXT_HTML_TAG, + CONTEXT_XML_ATTRIBUTE = self::CONTEXT_HTML_ATTRIBUTE, + CONTEXT_XML_COMMENT = self::CONTEXT_HTML_COMMENT, + CONTEXT_XML_BOGUS_COMMENT = self::CONTEXT_HTML_BOGUS_COMMENT; + + + /** + * Adds new macro with IMacro flags. + * @param string + * @return self + */ + public function addMacro($name, IMacro $macro, $flags = NULL) + { + if (!isset($this->flags[$name])) { + $this->flags[$name] = $flags ?: IMacro::DEFAULT_FLAGS; + } elseif ($flags && $this->flags[$name] !== $flags) { + throw new \LogicException("Incompatible flags for macro $name."); + } + $this->macros[$name][] = $macro; + return $this; + } + + + /** + * Compiles tokens to PHP code. + * @param Token[] + * @return string + */ + public function compile(array $tokens, $className) + { + $this->tokens = $tokens; + $output = ''; + $this->output = & $output; + $this->inHead = TRUE; + $this->htmlNode = $this->macroNode = $this->context = NULL; + $this->placeholders = $this->properties = []; + $this->methods = ['main' => NULL, 'prepare' => NULL]; + + $macroHandlers = new \SplObjectStorage; + array_map([$macroHandlers, 'attach'], call_user_func_array('array_merge', $this->macros)); + + foreach ($macroHandlers as $handler) { + $handler->initialize($this); + } + + foreach ($tokens as $this->position => $token) { + if ($this->inHead && !($token->type === $token::COMMENT + || $token->type === $token::MACRO_TAG && isset($this->flags[$token->name]) && $this->flags[$token->name] & IMacro::ALLOWED_IN_HEAD + || $token->type === $token::TEXT && trim($token->text) === '' + )) { + $this->inHead = FALSE; + } + $this->{"process$token->type"}($token); + } + + while ($this->htmlNode) { + if (!empty($this->htmlNode->macroAttrs)) { + throw new CompileException('Missing ' . self::printEndTag($this->htmlNode)); + } + $this->htmlNode = $this->htmlNode->parentNode; + } + + while ($this->macroNode) { + if (~$this->flags[$this->macroNode->name] & IMacro::AUTO_CLOSE) { + throw new CompileException('Missing ' . self::printEndTag($this->macroNode)); + } + $this->closeMacro($this->macroNode->name); + } + + $prepare = $epilogs = ''; + foreach ($macroHandlers as $handler) { + $res = $handler->finalize(); + $prepare .= empty($res[0]) ? '' : ""; + $epilogs = (empty($res[1]) ? '' : "") . $epilogs; + } + + $this->addMethod('main', $this->expandTokens("extract(\$this->params);?>\n$output$epilogsaddMethod('prepare', "extract(\$this->params);?>$preparecontentType !== self::CONTENT_HTML) { + $this->addProperty('contentType', $this->contentType); + } + + foreach ($this->properties as $name => $value) { + $members[] = "\tpublic $$name = " . PhpHelpers::dump($value) . ';'; + } + foreach (array_filter($this->methods) as $name => $method) { + $members[] = "\n\tfunction $name($method[arguments])\n\t{\n" . ($method['body'] ? "\t\t$method[body]\n" : '') . "\t}"; + } + + return "contentType = $type; + $this->context = NULL; + return $this; + } + + + /** + * @deprecated + */ + public function getContentType() + { + trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED); + return $this->contentType; + } + + + /** + * @internal + */ + public function setContext($context) + { + trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED); + $this->context = $context; + return $this; + } + + + /** + * @deprecated + */ + public function getContext() + { + trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED); + return $this->context; + } + + + /** + * @return MacroNode|NULL + */ + public function getMacroNode() + { + return $this->macroNode; + } + + + /** + * Returns current line number. + * @return int + */ + public function getLine() + { + return isset($this->tokens[$this->position]) ? $this->tokens[$this->position]->line : NULL; + } + + + /** + * @return bool + */ + public function isInHead() + { + return $this->inHead; + } + + + /** + * Adds custom method to template. + * @return void + * @internal + */ + public function addMethod($name, $body, $arguments = '') + { + $this->methods[$name] = ['body' => trim($body), 'arguments' => $arguments]; + } + + + /** + * Returns custom methods. + * @return array + * @internal + */ + public function getMethods() + { + return $this->methods; + } + + + /** + * Adds custom property to template. + * @return void + * @internal + */ + public function addProperty($name, $value) + { + $this->properties[$name] = $value; + } + + + /** + * Returns custom properites. + * @return array + * @internal + */ + public function getProperties() + { + return $this->properties; + } + + + /** @internal */ + public function expandTokens($s) + { + return strtr($s, $this->placeholders); + } + + + private function processText(Token $token) + { + if ($this->lastAttrValue === '' && Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE)) { + $this->lastAttrValue = $token->text; + } + $this->output .= $this->escape($token->text); + } + + + private function processMacroTag(Token $token) + { + if ($this->context === self::CONTEXT_HTML_TAG || Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE)) { + $this->lastAttrValue = TRUE; + } + + $isRightmost = !isset($this->tokens[$this->position + 1]) + || substr($this->tokens[$this->position + 1]->text, 0, 1) === "\n"; + + if ($token->closing) { + $this->closeMacro($token->name, $token->value, $token->modifiers, $isRightmost); + } else { + if (!$token->empty && isset($this->flags[$token->name]) && $this->flags[$token->name] & IMacro::AUTO_EMPTY) { + $pos = $this->position; + while (($t = isset($this->tokens[++$pos]) ? $this->tokens[$pos] : NULL) + && ($t->type !== Token::MACRO_TAG || $t->name !== $token->name) + && ($t->type !== Token::HTML_ATTRIBUTE_BEGIN || $t->name !== Parser::N_PREFIX . $token->name)); + $token->empty = $t ? !$t->closing : TRUE; + } + $node = $this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost); + if ($token->empty) { + if ($node->empty) { + throw new CompileException("Unexpected /} in tag {$token->text}"); + } + $this->closeMacro($token->name, NULL, NULL, $isRightmost); + } + } + } + + + private function processHtmlTagBegin(Token $token) + { + if ($token->closing) { + while ($this->htmlNode) { + if (strcasecmp($this->htmlNode->name, $token->name) === 0) { + break; + } + if ($this->htmlNode->macroAttrs) { + throw new CompileException("Unexpected $token->name>, expecting " . self::printEndTag($this->htmlNode)); + } + $this->htmlNode = $this->htmlNode->parentNode; + } + if (!$this->htmlNode) { + $this->htmlNode = new HtmlNode($token->name); + } + $this->htmlNode->closing = TRUE; + $this->htmlNode->endLine = $this->getLine(); + $this->context = self::CONTEXT_HTML_TEXT; + + } elseif ($token->text === ' + $this->addToken(Token::HTML_TAG_END, $matches[0]); + $this->setContext(self::CONTEXT_HTML_TEXT); + } else { + return $this->processMacro($matches); + } + } + + + /** + * Handles CONTEXT_NONE. + */ + private function contextNone() + { + $matches = $this->match('~ + (?P ' . $this->delimiters[0] . ') + ~xsi'); + return $this->processMacro($matches); + } + + + /** + * Handles CONTEXT_MACRO. + */ + private function contextMacro() + { + $matches = $this->match('~ + (?P \\*.*?\\*' . $this->delimiters[1] . '\n{0,2})| + (?P (?> + ' . self::RE_STRING . '| + \{(?>' . self::RE_STRING . '|[^\'"{}])*+\}| + [^\'"{}]+ + )++) + ' . $this->delimiters[1] . ' + (?P [ \t]*(?=\n))? + ~xsiA'); + + if (!empty($matches['macro'])) { + $token = $this->addToken(Token::MACRO_TAG, $this->context[1][1] . $matches[0]); + list($token->name, $token->value, $token->modifiers, $token->empty, $token->closing) = $this->parseMacroTag($matches['macro']); + $this->context = $this->context[1][0]; + + } elseif (!empty($matches['comment'])) { + $this->addToken(Token::COMMENT, $this->context[1][1] . $matches[0]); + $this->context = $this->context[1][0]; + + } else { + throw new CompileException('Malformed macro'); + } + } + + + private function processMacro($matches) + { + if (!empty($matches['macro'])) { // {macro} or {* *} + $this->setContext(self::CONTEXT_MACRO, [$this->context, $matches['macro']]); + } else { + return FALSE; + } + } + + + /** + * Matches next token. + * @param string + * @return array + */ + private function match($re) + { + if (!preg_match($re, $this->input, $matches, PREG_OFFSET_CAPTURE, $this->offset)) { + if (preg_last_error()) { + throw new RegexpException(NULL, preg_last_error()); + } + return []; + } + + $value = substr($this->input, $this->offset, $matches[0][1] - $this->offset); + if ($value !== '') { + $this->addToken(Token::TEXT, $value); + } + $this->offset = $matches[0][1] + strlen($matches[0][0]); + foreach ($matches as $k => $v) { + $matches[$k] = $v[0]; + } + return $matches; + } + + + /** + * @param string Parser::CONTENT_HTML, CONTENT_XHTML, CONTENT_XML or CONTENT_TEXT + * @return self + */ + public function setContentType($type) + { + if (in_array($type, [self::CONTENT_HTML, self::CONTENT_XHTML, self::CONTENT_XML], TRUE)) { + $this->setContext(self::CONTEXT_HTML_TEXT); + $this->xmlMode = $type === self::CONTENT_XML; + } else { + $this->setContext(self::CONTEXT_NONE); + } + return $this; + } + + + /** + * @return self + */ + public function setContext($context, $quote = NULL) + { + $this->context = [$context, $quote]; + return $this; + } + + + /** + * Changes macro tag delimiters. + * @param string + * @return self + */ + public function setSyntax($type) + { + $type = $type ?: $this->defaultSyntax; + if (isset($this->syntaxes[$type])) { + $this->setDelimiters($this->syntaxes[$type][0], $this->syntaxes[$type][1]); + } else { + throw new \InvalidArgumentException("Unknown syntax '$type'"); + } + return $this; + } + + + /** + * Changes macro tag delimiters. + * @param string left regular expression + * @param string right regular expression + * @return self + */ + public function setDelimiters($left, $right) + { + $this->delimiters = [$left, $right]; + return $this; + } + + + /** + * Parses macro tag to name, arguments a modifiers parts. + * @param string {name arguments | modifiers} + * @return array + * @internal + */ + public function parseMacroTag($tag) + { + if (!preg_match('~^ + (?P /?) + ( + (?P \?|[a-z]\w*+(?:[.:]\w+)*+(?!::|\(|\\\\))| ## ?, name, /name, but not function( or class:: or namespace\ + (?P !?)(?P [=\~#%^&_]?) ## !expression, !=expression, ... + )(?P (?:' . self::RE_STRING . '|[^\'"])*?) + (?P (?(?:' . self::RE_STRING . '|(?:\((?P>modArgs)\))|[^\'"/()]|/(?=.))*+))? + (?P /?\z) + ()\z~isx', $tag, $match)) { + if (preg_last_error()) { + throw new RegexpException(NULL, preg_last_error()); + } + return FALSE; + } + if ($match['name'] === '') { + $match['name'] = $match['shortname'] ?: ($match['closing'] ? '' : '='); + if ($match['noescape']) { + trigger_error("The noescape shortcut {!...} is deprecated, use {...|noescape} modifier on line {$this->getLine()}.", E_USER_DEPRECATED); + $match['modifiers'] .= '|noescape'; + } + } + return [$match['name'], trim($match['args']), $match['modifiers'], (bool) $match['empty'], (bool) $match['closing']]; + } + + + private function addToken($type, $text) + { + $this->output[] = $token = new Token; + $token->type = $type; + $token->text = $text; + $token->line = $this->getLine() - substr_count(ltrim($text), "\n"); + return $token; + } + + + public function getLine() + { + return $this->offset + ? substr_count(substr($this->input, 0, $this->offset - 1), "\n") + 1 + : 1; + } + + + /** + * Process low-level macros. + */ + protected function filter(Token $token) + { + if ($token->type === Token::MACRO_TAG && $token->name === '/syntax') { + $this->setSyntax($this->defaultSyntax); + $token->type = Token::COMMENT; + + } elseif ($token->type === Token::MACRO_TAG && $token->name === 'syntax') { + $this->setSyntax($token->value); + $token->type = Token::COMMENT; + + } elseif ($token->type === Token::HTML_ATTRIBUTE_BEGIN && $token->name === 'n:syntax') { + $this->setSyntax($token->value); + $this->syntaxEndTag = $this->lastHtmlTag; + $this->syntaxEndLevel = 1; + $token->type = Token::COMMENT; + + } elseif ($token->type === Token::HTML_TAG_BEGIN && $this->lastHtmlTag === $this->syntaxEndTag) { + $this->syntaxEndLevel++; + + } elseif ($token->type === Token::HTML_TAG_END && $this->lastHtmlTag === ('/' . $this->syntaxEndTag) && --$this->syntaxEndLevel === 0) { + $this->setSyntax($this->defaultSyntax); + + } elseif ($token->type === Token::MACRO_TAG && $token->name === 'contentType') { + if (strpos($token->value, 'html') !== FALSE) { + $this->setContentType(self::CONTENT_HTML); + } elseif (strpos($token->value, 'xml') !== FALSE) { + $this->setContentType(self::CONTENT_XML); + } else { + $this->setContentType(self::CONTENT_TEXT); + } + } + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Compiler/PhpHelpers.php php-nette-2.4-20160731/Nette/Latte/Compiler/PhpHelpers.php --- php-nette-2.3.10/Nette/Latte/Compiler/PhpHelpers.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler/PhpHelpers.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,129 @@ + $token) { + if (is_array($token)) { + list($name, $token) = ($tmp = $token); + if ($name === T_INLINE_HTML) { + $res .= $token; + + } elseif ($name === T_OPEN_TAG) { + $openLevel = $level; + + } elseif ($name === T_CLOSE_TAG) { + $next = isset($tokens[$n + 1]) ? $tokens[$n + 1] : NULL; + if (is_array($next) && $next[0] === T_OPEN_TAG) { // remove ?)next(); + + } else { + if (trim($php) !== '' || substr($res, -1) === '<') { // skip $v) { + $v = is_array($v) && (!$v || array_keys($v) === range(0, count($v) - 1)) + ? '[' . implode(', ', array_map(function($s) { return var_export($s, TRUE); }, $v)) . ']' + : var_export($v, TRUE); + $s .= "\t\t" . var_export($k, TRUE) . ' => ' . $v . ",\n"; + } + return $s. "\t]"; + } + return var_export($value, TRUE); + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Compiler/PhpWriter.php php-nette-2.4-20160731/Nette/Latte/Compiler/PhpWriter.php --- php-nette-2.3.10/Nette/Latte/Compiler/PhpWriter.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler/PhpWriter.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,527 @@ +tokenizer, NULL, $node->context); + $me->modifiers = & $node->modifiers; + return $me; + } + + + public function __construct(MacroTokens $tokens, $modifiers = NULL, array $context = NULL) + { + $this->tokens = $tokens; + $this->modifiers = $modifiers; + $this->context = $context; + } + + + /** + * Expands %node.word, %node.array, %node.args, %escape(), %modify(), %var, %raw, %word in code. + * @param string + * @return string + */ + public function write($mask) + { + $mask = preg_replace('#%(node|\d+)\.#', '%$1_', $mask); + $mask = preg_replace_callback('#%escape(\(([^()]*+|(?1))+\))#', function ($m) { + return $this->escapePass(new MacroTokens(substr($m[1], 1, -1)))->joinAll(); + }, $mask); + $mask = preg_replace_callback('#%modify(Content)?(\(([^()]*+|(?2))+\))#', function ($m) { + return $this->formatModifiers(substr($m[2], 1, -1), (bool) $m[1]); + }, $mask); + + $args = func_get_args(); + $pos = $this->tokens->position; + $word = strpos($mask, '%node_word') === FALSE ? NULL : $this->tokens->fetchWord(); + + $code = preg_replace_callback('#([,+]\s*)?%(node_|\d+_|)(word|var|raw|array|args)(\?)?(\s*\+\s*)?()#', + function ($m) use ($word, & $args) { + list(, $l, $source, $format, $cond, $r) = $m; + + switch ($source) { + case 'node_': + $arg = $word; break; + case '': + $arg = next($args); break; + default: + $arg = $args[(int) $source + 1]; break; + } + + switch ($format) { + case 'word': + $code = $this->formatWord($arg); break; + case 'args': + $code = $this->formatArgs(); break; + case 'array': + $code = $this->formatArray(); + $code = $cond && $code === '[]' ? '' : $code; break; + case 'var': + $code = var_export($arg, TRUE); break; + case 'raw': + $code = (string) $arg; break; + } + + if ($cond && $code === '') { + return $r ? $l : $r; + } else { + return $l . $code . $r; + } + }, $mask); + + $this->tokens->position = $pos; + return $code; + } + + + /** + * Formats modifiers calling. + * @param string + * @return string + */ + public function formatModifiers($var, $isContent = FALSE) + { + $tokens = new MacroTokens(ltrim($this->modifiers, '|')); + $tokens = $this->preprocess($tokens); + $tokens = $this->modifierPass($tokens, $var, $isContent); + $tokens = $this->quotingPass($tokens); + return $tokens->joinAll(); + } + + + /** + * Formats macro arguments to PHP code. (It advances tokenizer to the end as a side effect.) + * @return string + */ + public function formatArgs(MacroTokens $tokens = NULL) + { + $tokens = $this->preprocess($tokens); + $tokens = $this->quotingPass($tokens); + return $tokens->joinAll(); + } + + + /** + * Formats macro arguments to PHP array. (It advances tokenizer to the end as a side effect.) + * @return string + */ + public function formatArray(MacroTokens $tokens = NULL) + { + $tokens = $this->preprocess($tokens); + $tokens = $this->expandCastPass($tokens); + $tokens = $this->quotingPass($tokens); + return $tokens->joinAll(); + } + + + /** + * Formats parameter to PHP string. + * @param string + * @return string + */ + public function formatWord($s) + { + return (is_numeric($s) || preg_match('#^\$|[\'"]|^(true|TRUE)\z|^(false|FALSE)\z|^(null|NULL)\z|^[\w\\\\]{3,}::[A-Z0-9_]{2,}\z#', $s)) + ? $this->formatArgs(new MacroTokens($s)) + : '"' . $s . '"'; + } + + + /** + * Preprocessor for tokens. (It advances tokenizer to the end as a side effect.) + * @return MacroTokens + */ + public function preprocess(MacroTokens $tokens = NULL) + { + $tokens = $tokens === NULL ? $this->tokens : $tokens; + $this->validateTokens($tokens); + $tokens = $this->removeCommentsPass($tokens); + $tokens = $this->shortTernaryPass($tokens); + $tokens = $this->inlineModifierPass($tokens); + $tokens = $this->inOperatorPass($tokens); + return $tokens; + } + + + /** + * @throws CompileException + * @return void + */ + public function validateTokens(MacroTokens $tokens) + { + $deprecatedVars = array_flip(['$template', '$_b', '$_l', '$_g', '$_args', '$_fi', '$_control', '$_presenter', '$_form', '$_input', '$_label', '$_snippetMode']); + $brackets = []; + $pos = $tokens->position; + while ($tokens->nextToken()) { + if ($tokens->isCurrent('?>')) { + throw new CompileException('Forbidden ?> inside macro'); + + } elseif ($tokens->isCurrent($tokens::T_VARIABLE) && isset($deprecatedVars[$tokens->currentValue()])) { + trigger_error("Variable {$tokens->currentValue()} is deprecated.", E_USER_DEPRECATED); + + } elseif ($tokens->isCurrent($tokens::T_SYMBOL) + && !$tokens->isPrev('::') && !$tokens->isNext('::') && !$tokens->isPrev('->') + && preg_match('#^[A-Z0-9]{3,}$#', $val = $tokens->currentValue()) + ) { + trigger_error("Replace literal $val with constant('$val')", E_USER_DEPRECATED); + + } elseif ($tokens->isCurrent('(', '[', '{')) { + static $counterpart = ['(' => ')', '[' => ']', '{' => '}']; + $brackets[] = $counterpart[$tokens->currentValue()]; + + } elseif ($tokens->isCurrent(')', ']', '}') && $tokens->currentValue() !== array_pop($brackets)) { + throw new CompileException('Unexpected ' . $tokens->currentValue()); + + } elseif ($tokens->isCurrent('function', 'class', 'interface', 'trait') && $tokens->isNext($tokens::T_SYMBOL, '&') + || $tokens->isCurrent('return', 'yield') && !$brackets + ) { + throw new CompileException("Forbidden keyword '{$tokens->currentValue()}' inside macro."); + } + } + if ($brackets) { + throw new CompileException('Missing ' . array_pop($brackets)); + } + $tokens->position = $pos; + } + + + /** + * Removes PHP comments. + * @return MacroTokens + */ + public function removeCommentsPass(MacroTokens $tokens) + { + $res = new MacroTokens; + while ($tokens->nextToken()) { + if (!$tokens->isCurrent($tokens::T_COMMENT)) { + $res->append($tokens->currentToken()); + } + } + return $res; + } + + + /** + * Simplified ternary expressions without third part. + * @return MacroTokens + */ + public function shortTernaryPass(MacroTokens $tokens) + { + $res = new MacroTokens; + $inTernary = []; + while ($tokens->nextToken()) { + if ($tokens->isCurrent('?')) { + $inTernary[] = $tokens->depth; + + } elseif ($tokens->isCurrent(':')) { + array_pop($inTernary); + + } elseif ($tokens->isCurrent(',', ')', ']', '|') && end($inTernary) === $tokens->depth + $tokens->isCurrent(')', ']')) { + $res->append(' : NULL'); + array_pop($inTernary); + } + $res->append($tokens->currentToken()); + } + + if ($inTernary) { + $res->append(' : NULL'); + } + return $res; + } + + + /** + * Pseudocast (expand). + * @return MacroTokens + */ + public function expandCastPass(MacroTokens $tokens) + { + $res = new MacroTokens('['); + $expand = NULL; + while ($tokens->nextToken()) { + if ($tokens->isCurrent('(expand)') && $tokens->depth === 0) { + $expand = TRUE; + $res->append('],'); + } elseif ($expand && $tokens->isCurrent(',') && !$tokens->depth) { + $expand = FALSE; + $res->append(', ['); + } else { + $res->append($tokens->currentToken()); + } + } + + if ($expand === NULL) { + $res->append(']'); + } else { + $res->prepend('array_merge(')->append($expand ? ', [])' : '])'); + } + return $res; + } + + + /** + * Quotes symbols to strings. + * @return MacroTokens + */ + public function quotingPass(MacroTokens $tokens) + { + $res = new MacroTokens; + while ($tokens->nextToken()) { + $res->append($tokens->isCurrent($tokens::T_SYMBOL) + && (!$tokens->isPrev() || $tokens->isPrev(',', '(', '[', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', '=', 'and', 'or', 'xor', '??')) + && (!$tokens->isNext() || $tokens->isNext(',', ';', ')', ']', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', 'and', 'or', 'xor', '??')) + && !preg_match('#^[A-Z_][A-Z0-9_]{2,}$#', $tokens->currentValue()) + ? "'" . $tokens->currentValue() . "'" + : $tokens->currentToken() + ); + } + return $res; + } + + + /** + * Syntax $entry in [item1, item2]. + * @return MacroTokens + */ + public function inOperatorPass(MacroTokens $tokens) + { + while ($tokens->nextToken()) { + if ($tokens->isCurrent($tokens::T_VARIABLE)) { + $start = $tokens->position; + $depth = $tokens->depth; + $expr = $arr = []; + + $expr[] = $tokens->currentToken(); + while ($tokens->isNext($tokens::T_VARIABLE, $tokens::T_SYMBOL, $tokens::T_NUMBER, $tokens::T_STRING, '[', ']', '(', ')', '->') + && !$tokens->isNext('in')) { + $expr[] = $tokens->nextToken(); + } + + if ($depth === $tokens->depth && $tokens->nextValue('in') && ($arr[] = $tokens->nextToken('['))) { + while ($tokens->isNext()) { + $arr[] = $tokens->nextToken(); + if ($tokens->isCurrent(']') && $tokens->depth === $depth) { + $new = array_merge($tokens->parse('in_array('), $expr, $tokens->parse(', '), $arr, $tokens->parse(', TRUE)')); + array_splice($tokens->tokens, $start, $tokens->position - $start + 1, $new); + $tokens->position = $start + count($new) - 1; + continue 2; + } + } + } + $tokens->position = $start; + } + } + return $tokens->reset(); + } + + + /** + * Process inline filters ($var|filter) + * @return MacroTokens + */ + public function inlineModifierPass(MacroTokens $tokens) + { + $result = new MacroTokens; + while ($tokens->nextToken()) { + if ($tokens->isCurrent('(', '[')) { + $result->tokens = array_merge($result->tokens, $this->inlineModifierInner($tokens)); + } else { + $result->append($tokens->currentToken()); + } + } + return $result; + } + + + private function inlineModifierInner(MacroTokens $tokens) + { + $isFunctionOrArray = $tokens->isPrev($tokens::T_VARIABLE, $tokens::T_SYMBOL) || $tokens->isCurrent('['); + $result = new MacroTokens; + $args = new MacroTokens; + $modifiers = new MacroTokens; + $current = $args; + $anyModifier = FALSE; + $result->append($tokens->currentToken()); + + while ($tokens->nextToken()) { + if ($tokens->isCurrent('(', '[')) { + $current->tokens = array_merge($current->tokens, $this->inlineModifierInner($tokens)); + + } elseif ($current !== $modifiers && $tokens->isCurrent('|')) { + $anyModifier = TRUE; + $current = $modifiers; + + } elseif ($tokens->isCurrent(')', ']') || ($isFunctionOrArray && $tokens->isCurrent(','))) { + $partTokens = count($modifiers->tokens) + ? $this->modifierPass($modifiers, $args->tokens)->tokens + : $args->tokens; + $result->tokens = array_merge($result->tokens, $partTokens); + if ($tokens->isCurrent(',')) { + $result->append($tokens->currentToken()); + $args = new MacroTokens; + $modifiers = new MacroTokens; + $current = $args; + continue; + } elseif ($isFunctionOrArray || !$anyModifier) { + $result->append($tokens->currentToken()); + } else { + array_shift($result->tokens); + } + return $result->tokens; + + } else { + $current->append($tokens->currentToken()); + } + } + throw new CompileException('Unbalanced brackets.'); + } + + + /** + * Formats modifiers calling. + * @param MacroTokens + * @param string + * @throws CompileException + * @return MacroTokens + */ + public function modifierPass(MacroTokens $tokens, $var, $isContent = FALSE) + { + $inside = FALSE; + $res = new MacroTokens($var); + while ($tokens->nextToken()) { + if ($tokens->isCurrent($tokens::T_WHITESPACE)) { + $res->append(' '); + + } elseif ($inside) { + if ($tokens->isCurrent(':', ',')) { + $res->append(', '); + $tokens->nextAll($tokens::T_WHITESPACE); + + } elseif ($tokens->isCurrent('|')) { + $res->append(')'); + $inside = FALSE; + + } else { + $res->append($tokens->currentToken()); + } + } else { + if ($tokens->isCurrent($tokens::T_SYMBOL)) { + if ($tokens->isCurrent('escape')) { + if ($isContent) { + $res->prepend('LR\Filters::convertTo($_fi, ' . var_export(implode($this->context), TRUE) . ', ') + ->append(')'); + } else { + $res = $this->escapePass($res); + } + $tokens->nextToken('|'); + } elseif (!strcasecmp($tokens->currentValue(), 'checkurl')) { + $res->prepend('LR\Filters::safeUrl('); + $inside = TRUE; + } else { + $name = strtolower($tokens->currentValue()); + $res->prepend($isContent + ? '$this->filters->filterContent('. var_export($name, TRUE) . ', $_fi, ' + : 'call_user_func($this->filters->' . $name . ', ' + ); + $inside = TRUE; + } + } else { + throw new CompileException("Modifier name must be alphanumeric string, '{$tokens->currentValue()}' given."); + } + } + } + if ($inside) { + $res->append(')'); + } + return $res; + } + + + /** + * Escapes expression in tokens. + * @return MacroTokens + */ + public function escapePass(MacroTokens $tokens) + { + $tokens = clone $tokens; + list($contentType, $context) = $this->context; + switch ($contentType) { + case Compiler::CONTENT_XHTML: + case Compiler::CONTENT_HTML: + switch ($context) { + case Compiler::CONTEXT_HTML_TEXT: + return $tokens->prepend('LR\Filters::escapeHtmlText(')->append(')'); + case Compiler::CONTEXT_HTML_TAG: + case Compiler::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL: + return $tokens->prepend('LR\Filters::escapeHtmlAttrUnquoted(')->append(')'); + case Compiler::CONTEXT_HTML_ATTRIBUTE: + case Compiler::CONTEXT_HTML_ATTRIBUTE_URL: + return $tokens->prepend('LR\Filters::escapeHtmlAttr(')->append(')'); + case Compiler::CONTEXT_HTML_ATTRIBUTE_JS: + return $tokens->prepend('LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(')->append('))'); + case Compiler::CONTEXT_HTML_ATTRIBUTE_CSS: + return $tokens->prepend('LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(')->append('))'); + case Compiler::CONTEXT_HTML_COMMENT: + return $tokens->prepend('LR\Filters::escapeHtmlComment(')->append(')'); + case Compiler::CONTEXT_HTML_BOGUS_COMMENT: + return $tokens->prepend('LR\Filters::escapeHtml(')->append(')'); + case Compiler::CONTEXT_HTML_JS: + case Compiler::CONTEXT_HTML_CSS: + return $tokens->prepend('LR\Filters::escape' . ucfirst($context) . '(')->append(')'); + default: + throw new CompileException("Unknown context $contentType, $context."); + } + + case Compiler::CONTENT_XML: + switch ($context) { + case Compiler::CONTEXT_XML_TEXT: + case Compiler::CONTEXT_XML_ATTRIBUTE: + case Compiler::CONTEXT_XML_BOGUS_COMMENT: + return $tokens->prepend('LR\Filters::escapeXml(')->append(')'); + case Compiler::CONTEXT_XML_COMMENT: + return $tokens->prepend('LR\Filters::escapeHtmlComment(')->append(')'); + case Compiler::CONTEXT_XML_TAG: + return $tokens->prepend('LR\Filters::escapeXmlAttrUnquoted(')->append(')'); + default: + throw new CompileException("Unknown context $contentType, $context."); + } + + case Compiler::CONTENT_JS: + case Compiler::CONTENT_CSS: + case Compiler::CONTENT_ICAL: + return $tokens->prepend('LR\Filters::escape' . ucfirst($contentType) . '(')->append(')'); + case Compiler::CONTENT_TEXT: + return $tokens; + case NULL: + return $tokens->prepend('call_user_func($this->filters->escape, ')->append(')'); + default: + throw new CompileException("Unknown context $contentType."); + } + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Compiler/TokenIterator.php php-nette-2.4-20160731/Nette/Latte/Compiler/TokenIterator.php --- php-nette-2.3.10/Nette/Latte/Compiler/TokenIterator.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler/TokenIterator.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,249 @@ +tokens = $tokens; + } + + + /** + * Returns current token. + * @return array|NULL + */ + public function currentToken() + { + return isset($this->tokens[$this->position]) + ? $this->tokens[$this->position] + : NULL; + } + + + /** + * Returns current token value. + * @return string|NULL + */ + public function currentValue() + { + return isset($this->tokens[$this->position]) + ? $this->tokens[$this->position][Tokenizer::VALUE] + : NULL; + } + + + /** + * Returns next token. + * @param int|string (optional) desired token type or value + * @return array|NULL + */ + public function nextToken() + { + return $this->scan(func_get_args(), TRUE, TRUE); // onlyFirst, advance + } + + + /** + * Returns next token value. + * @param int|string (optional) desired token type or value + * @return string|NULL + */ + public function nextValue() + { + return $this->scan(func_get_args(), TRUE, TRUE, TRUE); // onlyFirst, advance, strings + } + + + /** + * Returns all next tokens. + * @param int|string (optional) desired token type or value + * @return array[] + */ + public function nextAll() + { + return $this->scan(func_get_args(), FALSE, TRUE); // advance + } + + + /** + * Returns all next tokens until it sees a given token type or value. + * @param int|string token type or value to stop before + * @return array[] + */ + public function nextUntil($arg) + { + return $this->scan(func_get_args(), FALSE, TRUE, FALSE, TRUE); // advance, until + } + + + /** + * Returns concatenation of all next token values. + * @param int|string (optional) token type or value to be joined + * @return string + */ + public function joinAll() + { + return $this->scan(func_get_args(), FALSE, TRUE, TRUE); // advance, strings + } + + + /** + * Returns concatenation of all next tokens until it sees a given token type or value. + * @param int|string token type or value to stop before + * @return string + */ + public function joinUntil($arg) + { + return $this->scan(func_get_args(), FALSE, TRUE, TRUE, TRUE); // advance, strings, until + } + + + /** + * Checks the current token. + * @param int|string token type or value + * @return bool + */ + public function isCurrent($arg) + { + if (!isset($this->tokens[$this->position])) { + return FALSE; + } + $args = func_get_args(); + $token = $this->tokens[$this->position]; + return in_array($token[Tokenizer::VALUE], $args, TRUE) + || (isset($token[Tokenizer::TYPE]) && in_array($token[Tokenizer::TYPE], $args, TRUE)); + } + + + /** + * Checks the next token existence. + * @param int|string (optional) token type or value + * @return bool + */ + public function isNext() + { + return (bool) $this->scan(func_get_args(), TRUE, FALSE); // onlyFirst + } + + + /** + * Checks the previous token existence. + * @param int|string (optional) token type or value + * @return bool + */ + public function isPrev() + { + return (bool) $this->scan(func_get_args(), TRUE, FALSE, FALSE, FALSE, TRUE); // onlyFirst, prev + } + + + /** + * Returns next expected token or throws exception. + * @param int|string (optional) desired token type or value + * @return string + * @throws CompileException + */ + public function expectNextValue() + { + if ($token = $this->scan(func_get_args(), TRUE, TRUE)) { // onlyFirst, advance + return $token[Tokenizer::VALUE]; + } + $pos = $this->position + 1; + while (isset($this->tokens[$pos]) && ($next = $this->tokens[$pos]) && in_array($next[Tokenizer::TYPE], $this->ignored, TRUE)) { + $pos++; + } + throw new CompileException("Unexpected token '" . $next[Tokenizer::VALUE] . "'."); + } + + + /** + * @return self + */ + public function reset() + { + $this->position = -1; + return $this; + } + + + /** + * Moves cursor to next token. + */ + protected function next() + { + $this->position++; + } + + + /** + * Looks for (first) (not) wanted tokens. + * @param array of desired token types or values + * @param bool + * @param bool + * @param bool + * @param bool + * @param bool + * @return mixed + */ + protected function scan($wanted, $onlyFirst, $advance, $strings = FALSE, $until = FALSE, $prev = FALSE) + { + $res = $onlyFirst ? NULL : ($strings ? '' : []); + $pos = $this->position + ($prev ? -1 : 1); + do { + if (!isset($this->tokens[$pos])) { + if (!$wanted && $advance && !$prev && $pos <= count($this->tokens)) { + $this->next(); + } + return $res; + } + + $token = $this->tokens[$pos]; + $type = isset($token[Tokenizer::TYPE]) ? $token[Tokenizer::TYPE] : NULL; + if (!$wanted || (in_array($token[Tokenizer::VALUE], $wanted, TRUE) || in_array($type, $wanted, TRUE)) ^ $until) { + while ($advance && !$prev && $pos > $this->position) { + $this->next(); + } + + if ($onlyFirst) { + return $strings ? $token[Tokenizer::VALUE] : $token; + } elseif ($strings) { + $res .= $token[Tokenizer::VALUE]; + } else { + $res[] = $token; + } + + } elseif ($until || !in_array($type, $this->ignored, TRUE)) { + return $res; + } + $pos += $prev ? -1 : 1; + } while (TRUE); + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Compiler/Tokenizer.php php-nette-2.4-20160731/Nette/Latte/Compiler/Tokenizer.php --- php-nette-2.3.10/Nette/Latte/Compiler/Tokenizer.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler/Tokenizer.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,88 @@ + pattern] + * @param string regular expression flag + */ + public function __construct(array $patterns, $flags = '') + { + $this->re = '~(' . implode(')|(', $patterns) . ')~A' . $flags; + $this->types = array_keys($patterns); + } + + + /** + * Tokenizes string. + * @param string + * @return array + */ + public function tokenize($input) + { + preg_match_all($this->re, $input, $tokens, PREG_SET_ORDER); + if (preg_last_error()) { + throw new RegexpException(NULL, preg_last_error()); + } + $len = 0; + $count = count($this->types); + foreach ($tokens as & $match) { + $type = NULL; + for ($i = 1; $i <= $count; $i++) { + if (!isset($match[$i])) { + break; + } elseif ($match[$i] != NULL) { + $type = $this->types[$i - 1]; + break; + } + } + $match = [self::VALUE => $match[0], self::OFFSET => $len, self::TYPE => $type]; + $len += strlen($match[self::VALUE]); + } + if ($len !== strlen($input)) { + list($line, $col) = $this->getCoordinates($input, $len); + $token = str_replace("\n", '\n', substr($input, $len, 10)); + throw new CompileException("Unexpected '$token' on line $line, column $col."); + } + return $tokens; + } + + + /** + * Returns position of token in input string. + * @param string + * @param int + * @return array of [line, column] + */ + public static function getCoordinates($text, $offset) + { + $text = substr($text, 0, $offset); + return [substr_count($text, "\n") + 1, $offset - strrpos("\n" . $text, "\n") + 1]; + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Compiler/Token.php php-nette-2.4-20160731/Nette/Latte/Compiler/Token.php --- php-nette-2.3.10/Nette/Latte/Compiler/Token.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler/Token.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,50 @@ +? used for types MACRO_TAG, HTML_TAG_BEGIN */ + public $closing; + + /** @var bool is tag empty {name/}? used for type MACRO_TAG */ + public $empty; + +} diff -Nru php-nette-2.3.10/Nette/Latte/Compiler.php php-nette-2.4-20160731/Nette/Latte/Compiler.php --- php-nette-2.3.10/Nette/Latte/Compiler.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Compiler.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,610 +0,0 @@ - IMacro[]] */ - private $macros; - - /** @var \SplObjectStorage */ - private $macroHandlers; - - /** @var HtmlNode */ - private $htmlNode; - - /** @var MacroNode */ - private $macroNode; - - /** @var string[] */ - private $attrCodes = array(); - - /** @var string */ - private $contentType; - - /** @var array [context, subcontext] */ - private $context; - - /** @var string */ - private $templateId; - - /** @var mixed */ - private $lastAttrValue; - - /** Context-aware escaping content types */ - const CONTENT_HTML = Engine::CONTENT_HTML, - CONTENT_XHTML = Engine::CONTENT_XHTML, - CONTENT_XML = Engine::CONTENT_XML, - CONTENT_JS = Engine::CONTENT_JS, - CONTENT_CSS = Engine::CONTENT_CSS, - CONTENT_URL = Engine::CONTENT_URL, - CONTENT_ICAL = Engine::CONTENT_ICAL, - CONTENT_TEXT = Engine::CONTENT_TEXT; - - /** @internal Context-aware escaping HTML contexts */ - const CONTEXT_COMMENT = 'comment', - CONTEXT_SINGLE_QUOTED_ATTR = "'", - CONTEXT_DOUBLE_QUOTED_ATTR = '"', - CONTEXT_UNQUOTED_ATTR = '='; - - - public function __construct() - { - $this->macroHandlers = new \SplObjectStorage; - } - - - /** - * Adds new macro. - * @param string - * @return self - */ - public function addMacro($name, IMacro $macro) - { - $this->macros[$name][] = $macro; - $this->macroHandlers->attach($macro); - return $this; - } - - - /** - * Compiles tokens to PHP code. - * @param Token[] - * @return string - */ - public function compile(array $tokens, $className) - { - $this->templateId = substr(md5($className), 0, 10); - $this->tokens = $tokens; - $output = ''; - $this->output = & $output; - $this->htmlNode = $this->macroNode = $this->context = NULL; - - foreach ($this->macroHandlers as $handler) { - $handler->initialize($this); - } - - foreach ($tokens as $this->position => $token) { - $this->{"process$token->type"}($token); - } - - while ($this->htmlNode) { - if (!empty($this->htmlNode->macroAttrs)) { - throw new CompileException('Missing ' . self::printEndTag($this->macroNode)); - } - $this->htmlNode = $this->htmlNode->parentNode; - } - - $prologs = $epilogs = ''; - foreach ($this->macroHandlers as $handler) { - $res = $handler->finalize(); - $handlerName = get_class($handler); - $prologs .= empty($res[0]) ? '' : ""; - $epilogs = (empty($res[1]) ? '' : "") . $epilogs; - } - $output = ($prologs ? $prologs . "\n" : '') . $output . $epilogs; - - if ($this->macroNode) { - throw new CompileException('Missing ' . self::printEndTag($this->macroNode)); - } - - $output = $this->expandTokens($output); - $output = "params as $__k => $__v) $$__k = $__v; unset($__k, $__v);' - . '?>' . $output . "contentType = $type; - $this->context = NULL; - return $this; - } - - - /** - * @return string - */ - public function getContentType() - { - return $this->contentType; - } - - - /** - * @return self - */ - public function setContext($context, $sub = NULL) - { - $this->context = array($context, $sub); - return $this; - } - - - /** - * @return array [context, subcontext] - */ - public function getContext() - { - return $this->context; - } - - - /** - * @return string - */ - public function getTemplateId() - { - return $this->templateId; - } - - - /** - * @return MacroNode|NULL - */ - public function getMacroNode() - { - return $this->macroNode; - } - - - /** - * Returns current line number. - * @return int - */ - public function getLine() - { - return $this->tokens ? $this->tokens[$this->position]->line : NULL; - } - - - /** @internal */ - public function expandTokens($s) - { - return strtr($s, $this->attrCodes); - } - - - private function processText(Token $token) - { - if (in_array($this->context[0], array(self::CONTEXT_SINGLE_QUOTED_ATTR, self::CONTEXT_DOUBLE_QUOTED_ATTR), TRUE)) { - if ($token->text === $this->context[0]) { - $this->setContext(self::CONTEXT_UNQUOTED_ATTR); - } elseif ($this->lastAttrValue === '') { - $this->lastAttrValue = $token->text; - } - } - $this->output .= $token->text; - } - - - private function processMacroTag(Token $token) - { - if (in_array($this->context[0], array(self::CONTEXT_SINGLE_QUOTED_ATTR, self::CONTEXT_DOUBLE_QUOTED_ATTR, self::CONTEXT_UNQUOTED_ATTR), TRUE)) { - $this->lastAttrValue = TRUE; - } - - $isRightmost = !isset($this->tokens[$this->position + 1]) - || substr($this->tokens[$this->position + 1]->text, 0, 1) === "\n"; - - if ($token->name[0] === '/') { - $this->closeMacro((string) substr($token->name, 1), $token->value, $token->modifiers, $isRightmost); - } else { - $this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost && !$token->empty); - if ($token->empty) { - $this->closeMacro($token->name, NULL, NULL, $isRightmost); - } - } - } - - - private function processHtmlTagBegin(Token $token) - { - if ($token->closing) { - while ($this->htmlNode) { - if (strcasecmp($this->htmlNode->name, $token->name) === 0) { - break; - } - if ($this->htmlNode->macroAttrs) { - throw new CompileException("Unexpected $token->name>, expecting " . self::printEndTag($this->macroNode)); - } - $this->htmlNode = $this->htmlNode->parentNode; - } - if (!$this->htmlNode) { - $this->htmlNode = new HtmlNode($token->name); - } - $this->htmlNode->closing = TRUE; - $this->htmlNode->offset = strlen($this->output); - $this->setContext(NULL); - - } elseif ($token->text === '') { - $this->output .= $token->text; - $this->setContext(NULL); - return; - } - - $htmlNode = $this->htmlNode; - $end = ''; - - if (!$htmlNode->closing) { - $htmlNode->isEmpty = strpos($token->text, '/') !== FALSE; - if (in_array($this->contentType, array(self::CONTENT_HTML, self::CONTENT_XHTML), TRUE)) { - $emptyElement = isset(Helpers::$emptyElements[strtolower($htmlNode->name)]); - $htmlNode->isEmpty = $htmlNode->isEmpty || $emptyElement; - if ($htmlNode->isEmpty) { // auto-correct - $space = substr(strstr($token->text, '>'), 1); - if ($emptyElement) { - $token->text = ($this->contentType === self::CONTENT_XHTML ? ' />' : '>') . $space; - } else { - $token->text = '>'; - $end = "$htmlNode->name>" . $space; - } - } - } - } - - if ($htmlNode->macroAttrs) { - $code = substr($this->output, $htmlNode->offset) . $token->text; - $this->output = substr($this->output, 0, $htmlNode->offset); - $this->writeAttrsMacro($code); - } else { - $this->output .= $token->text . $end; - } - - if ($htmlNode->isEmpty) { - $htmlNode->closing = TRUE; - if ($htmlNode->macroAttrs) { - $this->writeAttrsMacro($end); - } - } - - $this->setContext(NULL); - - if ($htmlNode->closing) { - $this->htmlNode = $this->htmlNode->parentNode; - - } elseif ((($lower = strtolower($htmlNode->name)) === 'script' || $lower === 'style') - && (!isset($htmlNode->attrs['type']) || preg_match('#(java|j|ecma|live)script|json|css#i', $htmlNode->attrs['type'])) - ) { - $this->setContext($lower === 'script' ? self::CONTENT_JS : self::CONTENT_CSS); - } - } - - - private function processHtmlAttribute(Token $token) - { - if (strncmp($token->name, Parser::N_PREFIX, strlen(Parser::N_PREFIX)) === 0) { - $name = substr($token->name, strlen(Parser::N_PREFIX)); - if (isset($this->htmlNode->macroAttrs[$name])) { - throw new CompileException("Found multiple attributes $token->name."); - - } elseif ($this->macroNode && $this->macroNode->htmlNode === $this->htmlNode) { - throw new CompileException("n:attributes must not appear inside macro; found $token->name inside {{$this->macroNode->name}}."); - } - $this->htmlNode->macroAttrs[$name] = $token->value; - return; - } - - $this->lastAttrValue = & $this->htmlNode->attrs[$token->name]; - $this->output .= $token->text; - - if (in_array($token->value, array(self::CONTEXT_SINGLE_QUOTED_ATTR, self::CONTEXT_DOUBLE_QUOTED_ATTR), TRUE)) { - $this->lastAttrValue = ''; - $contextMain = $token->value; - } else { - $this->lastAttrValue = $token->value; - $contextMain = self::CONTEXT_UNQUOTED_ATTR; - } - - $context = NULL; - if (in_array($this->contentType, array(self::CONTENT_HTML, self::CONTENT_XHTML), TRUE)) { - $lower = strtolower($token->name); - if (substr($lower, 0, 2) === 'on') { - $context = self::CONTENT_JS; - } elseif ($lower === 'style') { - $context = self::CONTENT_CSS; - } elseif (in_array($lower, array('href', 'src', 'action', 'formaction'), TRUE) - || ($lower === 'data' && strtolower($this->htmlNode->name) === 'object') - ) { - $context = self::CONTENT_URL; - } - } - - $this->setContext($contextMain, $context); - } - - - private function processComment(Token $token) - { - $isLeftmost = trim(substr($this->output, strrpos("\n$this->output", "\n"))) === ''; - if (!$isLeftmost) { - $this->output .= substr($token->text, strlen(rtrim($token->text, "\n"))); - } - } - - - /********************* macros ****************d*g**/ - - - /** - * Generates code for {macro ...} to the output. - * @param string - * @param string - * @param string - * @param bool - * @return MacroNode - * @internal - */ - public function openMacro($name, $args = NULL, $modifiers = NULL, $isRightmost = FALSE, $nPrefix = NULL) - { - $node = $this->expandMacro($name, $args, $modifiers, $nPrefix); - if ($node->isEmpty) { - $this->writeCode($node->openingCode, $this->output, $node->replaced, $isRightmost); - } else { - $this->macroNode = $node; - $node->saved = array(& $this->output, $isRightmost); - $this->output = & $node->content; - } - return $node; - } - - - /** - * Generates code for {/macro ...} to the output. - * @param string - * @param string - * @param string - * @param bool - * @return MacroNode - * @internal - */ - public function closeMacro($name, $args = NULL, $modifiers = NULL, $isRightmost = FALSE, $nPrefix = NULL) - { - $node = $this->macroNode; - - if (!$node || ($node->name !== $name && '' !== $name) || $modifiers - || ($args && $node->args && strncmp("$node->args ", "$args ", strlen($args) + 1)) - || $nPrefix !== $node->prefix - ) { - $name = $nPrefix - ? "{$this->htmlNode->name}> for " . Parser::N_PREFIX . implode(' and ' . Parser::N_PREFIX, array_keys($this->htmlNode->macroAttrs)) - : '{/' . $name . ($args ? ' ' . $args : '') . $modifiers . '}'; - throw new CompileException("Unexpected $name" . ($node ? ', expecting ' . self::printEndTag($node) : '')); - } - - $this->macroNode = $node->parentNode; - if (!$node->args) { - $node->setArgs($args); - } - - $isLeftmost = $node->content ? trim(substr($this->output, strrpos("\n$this->output", "\n"))) === '' : FALSE; - - $node->closing = TRUE; - $node->macro->nodeClosed($node); - - $this->output = & $node->saved[0]; - $this->writeCode($node->openingCode, $this->output, $node->replaced, $node->saved[1]); - $this->writeCode($node->closingCode, $node->content, $node->replaced, $isRightmost, $isLeftmost); - $this->output .= $node->content; - return $node; - } - - - private function writeCode($code, & $output, $replaced, $isRightmost, $isLeftmost = NULL) - { - if ($isRightmost) { - $leftOfs = strrpos("\n$output", "\n"); - if ($isLeftmost === NULL) { - $isLeftmost = trim(substr($output, $leftOfs)) === ''; - } - if ($replaced === NULL) { - $replaced = preg_match('#<\?php.*\secho\s#As', $code); - } - if ($isLeftmost && !$replaced) { - $output = substr($output, 0, $leftOfs); // alone macro without output -> remove indentation - } elseif (substr($code, -2) === '?>') { - $code .= "\n"; // double newline to avoid newline eating by PHP - } - } - $output .= $code; - } - - - /** - * Generates code for macro to the output. - * @param string - * @return void - * @internal - */ - public function writeAttrsMacro($code) - { - $attrs = $this->htmlNode->macroAttrs; - $left = $right = array(); - - foreach ($this->macros as $name => $foo) { - $attrName = MacroNode::PREFIX_INNER . "-$name"; - if (isset($attrs[$attrName])) { - if ($this->htmlNode->closing) { - $left[] = array('closeMacro', $name, '', MacroNode::PREFIX_INNER); - } else { - array_unshift($right, array('openMacro', $name, $attrs[$attrName], MacroNode::PREFIX_INNER)); - } - unset($attrs[$attrName]); - } - } - - foreach (array_reverse($this->macros) as $name => $foo) { - $attrName = MacroNode::PREFIX_TAG . "-$name"; - if (isset($attrs[$attrName])) { - $left[] = array('openMacro', $name, $attrs[$attrName], MacroNode::PREFIX_TAG); - array_unshift($right, array('closeMacro', $name, '', MacroNode::PREFIX_TAG)); - unset($attrs[$attrName]); - } - } - - foreach ($this->macros as $name => $foo) { - if (isset($attrs[$name])) { - if ($this->htmlNode->closing) { - $right[] = array('closeMacro', $name, '', MacroNode::PREFIX_NONE); - } else { - array_unshift($left, array('openMacro', $name, $attrs[$name], MacroNode::PREFIX_NONE)); - } - unset($attrs[$name]); - } - } - - if ($attrs) { - throw new CompileException('Unknown attribute ' . Parser::N_PREFIX - . implode(' and ' . Parser::N_PREFIX, array_keys($attrs))); - } - - if (!$this->htmlNode->closing) { - $this->htmlNode->attrCode = & $this->attrCodes[$uniq = ' n:' . substr(lcg_value(), 2, 10)]; - $code = substr_replace($code, $uniq, strrpos($code, '/>') ?: strrpos($code, '>'), 0); - } - - foreach ($left as $item) { - $node = $this->{$item[0]}($item[1], $item[2], NULL, NULL, $item[3]); - if ($node->closing || $node->isEmpty) { - $this->htmlNode->attrCode .= $node->attrCode; - if ($node->isEmpty) { - unset($this->htmlNode->macroAttrs[$node->name]); - } - } - } - - $this->output .= $code; - - foreach ($right as $item) { - $node = $this->{$item[0]}($item[1], $item[2], NULL, NULL, $item[3]); - if ($node->closing) { - $this->htmlNode->attrCode .= $node->attrCode; - } - } - - if ($right && substr($this->output, -2) === '?>') { - $this->output .= "\n"; - } - } - - - /** - * Expands macro and returns node & code. - * @param string - * @param string - * @param string - * @return MacroNode - * @internal - */ - public function expandMacro($name, $args, $modifiers = NULL, $nPrefix = NULL) - { - $inScript = in_array($this->context[0], array(self::CONTENT_JS, self::CONTENT_CSS), TRUE); - - if (empty($this->macros[$name])) { - $hint = ($t = Helpers::getSuggestion(array_keys($this->macros), $name)) ? ", did you mean {{$t}}?" : ''; - throw new CompileException("Unknown macro {{$name}}$hint" . ($inScript ? ' (in JavaScript or CSS, try to put a space after bracket or use n:syntax=off)' : '')); - } - - if (strpbrk($name, '=~%^&_')) { - if ($this->context[1] === self::CONTENT_URL) { - $modifiers = preg_replace('#\|nosafeurl\s?(?=\||\z)#i', '', $modifiers, -1, $found); - if (!$found && !preg_match('#\|datastream(?=\s|\||\z)#i', $modifiers)) { - $modifiers .= '|safeurl'; - } - } - - $modifiers = preg_replace('#\|noescape\s?(?=\||\z)#i', '', $modifiers, -1, $found); - if (!$found) { - $modifiers .= '|escape'; - } - - if (!$found && $inScript && $name === '=' && preg_match('#["\'] *\z#', $this->tokens[$this->position - 1]->text)) { - throw new CompileException("Do not place {$this->tokens[$this->position]->text} inside quotes."); - } - } - - foreach (array_reverse($this->macros[$name]) as $macro) { - $node = new MacroNode($macro, $name, $args, $modifiers, $this->macroNode, $this->htmlNode, $nPrefix); - if ($macro->nodeOpened($node) !== FALSE) { - return $node; - } - } - - throw new CompileException('Unknown ' . ($nPrefix - ? 'attribute ' . Parser::N_PREFIX . ($nPrefix === MacroNode::PREFIX_NONE ? '' : "$nPrefix-") . $name - : 'macro {' . $name . ($args ? " $args" : '') . '}' - )); - } - - - private static function printEndTag(MacroNode $node) - { - if ($node->prefix) { - return "{$node->htmlNode->name}> for " . Parser::N_PREFIX - . implode(' and ' . Parser::N_PREFIX, array_keys($node->htmlNode->macroAttrs)); - } else { - return "{/$node->name}"; - } - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Engine.php php-nette-2.4-20160731/Nette/Latte/Engine.php --- php-nette-2.3.10/Nette/Latte/Engine.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Engine.php 2016-07-31 17:46:38.000000000 +0000 @@ -11,9 +11,11 @@ /** * Templating engine Latte. */ -class Engine extends Object +class Engine { - const VERSION = '2.3.11'; + use Strict; + + const VERSION = '2.4.1'; /** Content types */ const CONTENT_HTML = 'html', @@ -21,12 +23,11 @@ CONTENT_XML = 'xml', CONTENT_JS = 'js', CONTENT_CSS = 'css', - CONTENT_URL = 'url', CONTENT_ICAL = 'ical', CONTENT_TEXT = 'text'; /** @var callable[] */ - public $onCompile = array(); + public $onCompile = []; /** @var Parser */ private $parser; @@ -37,6 +38,12 @@ /** @var ILoader */ private $loader; + /** @var Runtime\FilterExecutor */ + private $filters; + + /** @var array */ + private $providers = []; + /** @var string */ private $contentType = self::CONTENT_HTML; @@ -46,52 +53,22 @@ /** @var bool */ private $autoRefresh = TRUE; - /** @var array run-time filters */ - private $filters = array( - NULL => array(), // dynamic - 'bytes' => 'Latte\Runtime\Filters::bytes', - 'capitalize' => 'Latte\Runtime\Filters::capitalize', - 'datastream' => 'Latte\Runtime\Filters::dataStream', - 'date' => 'Latte\Runtime\Filters::date', - 'escapecss' => 'Latte\Runtime\Filters::escapeCss', - 'escapehtml' => 'Latte\Runtime\Filters::escapeHtml', - 'escapehtmlcomment' => 'Latte\Runtime\Filters::escapeHtmlComment', - 'escapeical' => 'Latte\Runtime\Filters::escapeICal', - 'escapejs' => 'Latte\Runtime\Filters::escapeJs', - 'escapeurl' => 'rawurlencode', - 'escapexml' => 'Latte\Runtime\Filters::escapeXML', - 'firstupper' => 'Latte\Runtime\Filters::firstUpper', - 'implode' => 'implode', - 'indent' => 'Latte\Runtime\Filters::indent', - 'lower' => 'Latte\Runtime\Filters::lower', - 'nl2br' => 'Latte\Runtime\Filters::nl2br', - 'number' => 'number_format', - 'repeat' => 'str_repeat', - 'replace' => 'Latte\Runtime\Filters::replace', - 'replacere' => 'Latte\Runtime\Filters::replaceRe', - 'safeurl' => 'Latte\Runtime\Filters::safeUrl', - 'strip' => 'Latte\Runtime\Filters::strip', - 'striptags' => 'strip_tags', - 'substr' => 'Latte\Runtime\Filters::substring', - 'trim' => 'Latte\Runtime\Filters::trim', - 'truncate' => 'Latte\Runtime\Filters::truncate', - 'upper' => 'Latte\Runtime\Filters::upper', - ); + + + public function __construct() + { + $this->filters = new Runtime\FilterExecutor; + } /** * Renders template to output. * @return void */ - public function render($name, array $params = array()) + public function render($name, array $params = [], $block = NULL) { - $class = $this->getTemplateClass($name); - if (!class_exists($class, FALSE)) { - $this->loadTemplate($name); - } - - $template = new $class($params, $this, $name); - $template->render(); + $this->createTemplate($name, $params + ['_renderblock' => $block]) + ->render(); } @@ -99,19 +76,24 @@ * Renders template to string. * @return string */ - public function renderToString($name, array $params = array()) + public function renderToString($name, array $params = [], $block = NULL) { - ob_start(function () {}); - try { - $this->render($name, $params); - } catch (\Throwable $e) { - ob_end_clean(); - throw $e; - } catch (\Exception $e) { - ob_end_clean(); - throw $e; + $template = $this->createTemplate($name, $params + ['_renderblock' => $block]); + return $template->capture([$template, 'render']); + } + + + /** + * Creates template object. + * @return Runtime\Template + */ + public function createTemplate($name, array $params = []) + { + $class = $this->getTemplateClass($name); + if (!class_exists($class, FALSE)) { + $this->loadTemplate($name); } - return ob_get_clean(); + return new $class($this, $params, $this->filters, $this->providers, $name); } @@ -121,10 +103,10 @@ */ public function compile($name) { - foreach ($this->onCompile ?: array() as $cb) { + foreach ($this->onCompile ?: [] as $cb) { call_user_func(Helpers::checkCallback($cb), $this); } - $this->onCompile = array(); + $this->onCompile = []; $source = $this->getLoader()->getContent($name); @@ -146,7 +128,7 @@ if (!preg_match('#\n|\?#', $name)) { $code = "" . $code; } - $code = Helpers::optimizePhp($code); + $code = PhpHelpers::reformatCode($code); return $code; } @@ -178,9 +160,8 @@ if (!$this->tempDirectory) { $code = $this->compile($name); if (@eval('?>' . $code) === FALSE) { // @ is escalated to exception - $error = error_get_last(); - $e = new CompileException('Error in template: ' . $error['message']); - throw $e->setSource($code, $error['line'], "$name (compiled)"); + throw (new CompileException('Error in template: ' . error_get_last()['message'])) + ->setSource($code, error_get_last()['line'], "$name (compiled)"); } return; } @@ -205,6 +186,8 @@ if (file_put_contents("$file.tmp", $code) !== strlen($code) || !rename("$file.tmp", $file)) { @unlink("$file.tmp"); // @ - file may not exist throw new \RuntimeException("Unable to create '$file'."); + } elseif (function_exists('opcache_invalidate')) { + @opcache_invalidate($file, TRUE); // @ can be restricted } } @@ -213,6 +196,8 @@ } flock($handle, LOCK_UN); + fclose($handle); + @unlink("$file.lock"); // @ file may become locked on Windows } @@ -232,11 +217,11 @@ */ public function getCacheFile($name) { - $file = $this->getTemplateClass($name); - if (preg_match('#\b\w.{10,50}$#', $name, $m)) { - $file = trim(preg_replace('#\W+#', '-', $m[0]), '-') . '-' . $file; - } - return $this->tempDirectory . '/' . $file . '.php'; + $hash = substr($this->getTemplateClass($name), 8); + $base = preg_match('#([/\\\\][\w@.-]{3,35}){1,3}\z#', $name, $m) + ? preg_replace('#[^\w@.-]+#', '-', substr($m[0], 1)) . '--' + : ''; + return "$this->tempDirectory/$base$hash.php"; } @@ -245,7 +230,8 @@ */ public function getTemplateClass($name) { - return 'Template' . md5("$this->tempDirectory\00$name"); + $key = $this->getLoader()->getUniqueId($name) . "\00" . self::VERSION; + return 'Template' . substr(md5($key), 0, 10); } @@ -257,22 +243,18 @@ */ public function addFilter($name, $callback) { - if ($name == NULL) { // intentionally == - array_unshift($this->filters[NULL], $callback); - } else { - $this->filters[strtolower($name)] = $callback; - } + $this->filters->add($name, $callback); return $this; } /** * Returns all run-time filters. - * @return callable[] + * @return string[] */ public function getFilters() { - return $this->filters; + return $this->filters->getAll(); } @@ -284,22 +266,7 @@ */ public function invokeFilter($name, array $args) { - $lname = strtolower($name); - if (!isset($this->filters[$lname])) { - $args2 = $args; - array_unshift($args2, $lname); - foreach ($this->filters[NULL] as $filter) { - $res = call_user_func_array(Helpers::checkCallback($filter), $args2); - if ($res !== NULL) { - return $res; - } elseif (isset($this->filters[$lname])) { - return call_user_func_array(Helpers::checkCallback($this->filters[$lname]), $args); - } - } - $hint = ($t = Helpers::getSuggestion(array_keys($this->filters), $name)) ? ", did you mean '$t'?" : '.'; - throw new \LogicException("Filter '$name' is not defined$hint"); - } - return call_user_func_array(Helpers::checkCallback($this->filters[$lname]), $args); + return call_user_func_array($this->filters->$name, $args); } @@ -314,6 +281,27 @@ } + /** + * Adds new provider. + * @return self + */ + public function addProvider($name, $value) + { + $this->providers[$name] = $value; + return $this; + } + + + /** + * Returns all providers. + * @return array + */ + public function getProviders() + { + return $this->providers; + } + + /** * @return self */ diff -Nru php-nette-2.3.10/Nette/Latte/exceptions.php php-nette-2.4-20160731/Nette/Latte/exceptions.php --- php-nette-2.3.10/Nette/Latte/exceptions.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/exceptions.php 2016-07-31 17:46:38.000000000 +0000 @@ -43,13 +43,13 @@ */ class RegexpException extends \Exception { - public static $messages = array( + public static $messages = [ PREG_INTERNAL_ERROR => 'Internal error', PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted', PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted', PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data', 5 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', // PREG_BAD_UTF8_OFFSET_ERROR - ); + ]; public function __construct($message, $code = NULL) { diff -Nru php-nette-2.3.10/Nette/Latte/Helpers.php php-nette-2.4-20160731/Nette/Latte/Helpers.php --- php-nette-2.3.10/Nette/Latte/Helpers.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Helpers.php 2016-07-31 17:46:38.000000000 +0000 @@ -15,10 +15,10 @@ class Helpers { /** @var array empty (void) HTML elements */ - public static $emptyElements = array( + public static $emptyElements = [ 'img' => 1,'hr' => 1,'br' => 1,'input' => 1,'meta' => 1,'area' => 1,'embed' => 1,'keygen' => 1,'source' => 1,'base' => 1, 'col' => 1,'link' => 1,'param' => 1,'basefont' => 1,'frame' => 1,'isindex' => 1,'wbr' => 1,'command' => 1,'track' => 1, - ); + ]; /** @@ -35,83 +35,6 @@ /** - * Removes unnecessary blocks of PHP code. - * @param string - * @return string - */ - public static function optimizePhp($source, $lineLength = 80) - { - $res = $php = ''; - $lastChar = ';'; - $tokens = new \ArrayIterator(token_get_all($source)); - foreach ($tokens as $n => $token) { - if (is_array($token)) { - if ($token[0] === T_INLINE_HTML) { - $lastChar = ''; - $res .= $token[1]; - - } elseif ($token[0] === T_CLOSE_TAG) { - $next = isset($tokens[$n + 1]) ? $tokens[$n + 1] : NULL; - if (substr($res, -1) !== '<' && preg_match('#^<\?php\s*\z#', $php)) { - $php = ''; // removes empty (?php ?), but retains ((?php ?)?php - - } elseif (is_array($next) && $next[0] === T_OPEN_TAG && (!isset($tokens[$n + 2][1]) || $tokens[$n + 2][1] !== 'xml')) { // remove ?)(?php - if (!strspn($lastChar, ';{}:/')) { - $php .= $lastChar = ';'; - } - if (substr($next[1], -1) === "\n") { - $php .= "\n"; - } - $tokens->next(); - - } elseif ($next) { - $res .= preg_replace('#;?(\s)*\z#', '$1', $php) . $token[1]; // remove last semicolon before ?) - if (strlen($res) - strrpos($res, "\n") > $lineLength - && (!is_array($next) || strpos($next[1], "\n") === FALSE) - ) { - $res .= "\n"; - } - $php = ''; - - } else { // remove last ?) - if (!strspn($lastChar, '};')) { - $php .= ';'; - } - } - - } elseif ($token[0] === T_ELSE || $token[0] === T_ELSEIF) { - if ($tokens[$n + 1] === ':' && $lastChar === '}') { - $php .= ';'; // semicolon needed in if(): ... if() ... else: - } - $lastChar = ''; - $php .= $token[1]; - - } elseif ($token[0] === T_OPEN_TAG && $token[1] === '' && isset($tokens[$n + 1][1]) && $tokens[$n + 1][1] === 'xml') { - $lastChar = ''; - $res .= '<?'; - for ($tokens->next(); $tokens->valid(); $tokens->next()) { - $token = $tokens->current(); - $res .= is_array($token) ? $token[1] : $token; - if ($token[0] === T_CLOSE_TAG) { - break; - } - } - - } else { - if (!in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG), TRUE)) { - $lastChar = ''; - } - $php .= $token[1]; - } - } else { - $php .= $lastChar = $token; - } - } - return $res . $php; - } - - - /** * Finds the best suggestion. * @return string|NULL */ @@ -129,4 +52,24 @@ return $best; } + + /** + * @return bool + */ + public static function removeFilter(& $modifier, $filter) + { + $modifier = preg_replace('#\|(' . $filter . ')\s?(?=\||\z)#i', '', $modifier, -1, $found); + return (bool) $found; + } + + + /** + * Starts the $haystack string with the prefix $needle? + * @return bool + */ + public static function startsWith($haystack, $needle) + { + return strncmp($haystack, $needle, strlen($needle)) === 0; + } + } diff -Nru php-nette-2.3.10/Nette/Latte/HtmlNode.php php-nette-2.4-20160731/Nette/Latte/HtmlNode.php --- php-nette-2.3.10/Nette/Latte/HtmlNode.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/HtmlNode.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -name = $name; - $this->parentNode = $parentNode; - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/ILoader.php php-nette-2.4-20160731/Nette/Latte/ILoader.php --- php-nette-2.3.10/Nette/Latte/ILoader.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/ILoader.php 2016-07-31 17:46:38.000000000 +0000 @@ -27,9 +27,15 @@ function isExpired($name, $time); /** - * Returns fully qualified template name. + * Returns referred template name. * @return string */ - function getChildName($name, $parent = NULL); + function getReferredName($name, $referringName); + + /** + * Returns unique identifier for caching. + * @return string + */ + function getUniqueId($name); } diff -Nru php-nette-2.3.10/Nette/Latte/IMacro.php php-nette-2.4-20160731/Nette/Latte/IMacro.php --- php-nette-2.3.10/Nette/Latte/IMacro.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/IMacro.php 2016-07-31 17:46:38.000000000 +0000 @@ -13,6 +13,11 @@ */ interface IMacro { + const + AUTO_EMPTY = 4, + AUTO_CLOSE = 64, + ALLOWED_IN_HEAD = 128, + DEFAULT_FLAGS = 0; /** * Initializes before template parsing. diff -Nru php-nette-2.3.10/Nette/Latte/Loaders/FileLoader.php php-nette-2.4-20160731/Nette/Latte/Loaders/FileLoader.php --- php-nette-2.3.10/Nette/Latte/Loaders/FileLoader.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Loaders/FileLoader.php 2016-07-31 17:46:38.000000000 +0000 @@ -13,8 +13,19 @@ /** * Template loader. */ -class FileLoader extends Latte\Object implements Latte\ILoader +class FileLoader implements Latte\ILoader { + use Latte\Strict; + + /** @var string|NULL */ + private $baseDir; + + + public function __construct($baseDir = NULL) + { + $this->baseDir = $baseDir ? $this->normalizePath("$baseDir/") : NULL; + } + /** * Returns template source code. @@ -22,13 +33,16 @@ */ public function getContent($file) { - if (!is_file($file)) { + $file = $this->baseDir . $file; + if ($this->baseDir && !Latte\Helpers::startsWith($this->normalizePath($file), $this->baseDir)) { + throw new \RuntimeException("Template '$file' is not within the allowed path '$this->baseDir'."); + + } elseif (!is_file($file)) { throw new \RuntimeException("Missing template file '$file'."); } elseif ($this->isExpired($file, time())) { if (@touch($file) === FALSE) { - $tmp = error_get_last(); - trigger_error("File's modification time is in the future. Cannot update it: $tmp[message]", E_USER_WARNING); + trigger_error("File's modification time is in the future. Cannot update it: " . error_get_last()['message'], E_USER_WARNING); } } return file_get_contents($file); @@ -40,20 +54,47 @@ */ public function isExpired($file, $time) { - return @filemtime($file) > $time; // @ - stat may fail + return @filemtime($this->baseDir . $file) > $time; // @ - stat may fail } /** - * Returns fully qualified template name. + * Returns referred template name. * @return string */ - public function getChildName($file, $parent = NULL) + public function getReferredName($file, $referringFile) { - if ($parent && !preg_match('#/|\\\\|[a-z][a-z0-9+.-]*:#iA', $file)) { - $file = dirname($parent) . '/' . $file; + if ($this->baseDir || !preg_match('#/|\\\\|[a-z][a-z0-9+.-]*:#iA', $file)) { + $file = $this->normalizePath($referringFile . '/../' . $file); } return $file; } + + /** + * Returns unique identifier for caching. + * @return string + */ + public function getUniqueId($file) + { + return $this->baseDir . strtr($file, '/', DIRECTORY_SEPARATOR); + } + + + /** + * @return string + */ + private static function normalizePath($path) + { + $res = []; + foreach (explode('/', strtr($path, '\\', '/')) as $part) { + if ($part === '..' && $res) { + array_pop($res); + } elseif ($part !== '.') { + $res[] = $part; + } + } + return implode(DIRECTORY_SEPARATOR, $res); + } + } diff -Nru php-nette-2.3.10/Nette/Latte/Loaders/StringLoader.php php-nette-2.4-20160731/Nette/Latte/Loaders/StringLoader.php --- php-nette-2.3.10/Nette/Latte/Loaders/StringLoader.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Loaders/StringLoader.php 2016-07-31 17:46:38.000000000 +0000 @@ -13,35 +13,62 @@ /** * Template loader. */ -class StringLoader extends Latte\Object implements Latte\ILoader +class StringLoader implements Latte\ILoader { + use Latte\Strict; + + /** @var array|NULL [name => content] */ + private $templates; + + + public function __construct(array $templates = NULL) + { + $this->templates = $templates; + } + /** * Returns template source code. * @return string */ - public function getContent($content) + public function getContent($name) { - return $content; + if ($this->templates === NULL) { + return $name; + } elseif (isset($this->templates[$name])) { + return $this->templates[$name]; + } else { + throw new \RuntimeException("Missing template '$name'."); + } } /** * @return bool */ - public function isExpired($content, $time) + public function isExpired($name, $time) { return FALSE; } /** - * Returns fully qualified template name. + * Returns referred template name. + * @return string + */ + public function getReferredName($name, $referringName) + { + return $name; + } + + + /** + * Returns unique identifier for caching. * @return string */ - public function getChildName($content, $parent = NULL) + public function getUniqueId($name) { - return $content; + return $this->getContent($name); } } diff -Nru php-nette-2.3.10/Nette/Latte/MacroNode.php php-nette-2.4-20160731/Nette/Latte/MacroNode.php --- php-nette-2.3.10/Nette/Latte/MacroNode.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/MacroNode.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -macro = $macro; - $this->name = (string) $name; - $this->modifiers = (string) $modifiers; - $this->parentNode = $parentNode; - $this->htmlNode = $htmlNode; - $this->prefix = $prefix; - $this->data = new \stdClass; - $this->setArgs($args); - } - - - public function setArgs($args) - { - $this->args = (string) $args; - $this->tokenizer = new MacroTokens($this->args); - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Macros/BlockMacros.php php-nette-2.4-20160731/Nette/Latte/Macros/BlockMacros.php --- php-nette-2.3.10/Nette/Latte/Macros/BlockMacros.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Macros/BlockMacros.php 2016-07-31 17:46:38.000000000 +0000 @@ -8,9 +8,12 @@ namespace Latte\Macros; use Latte; +use Latte\CompileException; +use Latte\Engine; +use Latte\Helpers; use Latte\MacroNode; use Latte\PhpWriter; -use Latte\CompileException; +use Latte\Runtime\SnippetDriver; /** @@ -19,25 +22,32 @@ class BlockMacros extends MacroSet { /** @var array */ - private $namedBlocks = array(); + private $namedBlocks = []; + + /** @var array */ + private $blockTypes = []; /** @var bool */ private $extends; + /** @var string[] */ + private $imports; + public static function install(Latte\Compiler $compiler) { $me = new static($compiler); - $me->addMacro('include', array($me, 'macroInclude')); - $me->addMacro('includeblock', array($me, 'macroIncludeBlock')); - $me->addMacro('extends', array($me, 'macroExtends')); - $me->addMacro('layout', array($me, 'macroExtends')); - $me->addMacro('block', array($me, 'macroBlock'), array($me, 'macroBlockEnd')); - $me->addMacro('define', array($me, 'macroBlock'), array($me, 'macroBlockEnd')); - $me->addMacro('snippet', array($me, 'macroBlock'), array($me, 'macroBlockEnd')); - $me->addMacro('snippetArea', array($me, 'macroBlock'), array($me, 'macroBlockEnd')); - $me->addMacro('ifset', array($me, 'macroIfset'), '}'); - $me->addMacro('elseifset', array($me, 'macroIfset'), '}'); + $me->addMacro('include', [$me, 'macroInclude']); + $me->addMacro('includeblock', [$me, 'macroIncludeBlock']); // deprecated + $me->addMacro('import', [$me, 'macroImport'], NULL, NULL, self::ALLOWED_IN_HEAD); + $me->addMacro('extends', [$me, 'macroExtends'], NULL, NULL, self::ALLOWED_IN_HEAD); + $me->addMacro('layout', [$me, 'macroExtends'], NULL, NULL, self::ALLOWED_IN_HEAD); + $me->addMacro('snippet', [$me, 'macroBlock'], [$me, 'macroBlockEnd']); + $me->addMacro('block', [$me, 'macroBlock'], [$me, 'macroBlockEnd'], NULL, self::AUTO_CLOSE); + $me->addMacro('define', [$me, 'macroBlock'], [$me, 'macroBlockEnd']); + $me->addMacro('snippetArea', [$me, 'macroBlock'], [$me, 'macroBlockEnd']); + $me->addMacro('ifset', [$me, 'macroIfset'], '}'); + $me->addMacro('elseifset', [$me, 'macroIfset']); } @@ -47,52 +57,36 @@ */ public function initialize() { - $this->namedBlocks = array(); + $this->namedBlocks = []; + $this->blockTypes = []; $this->extends = NULL; + $this->imports = []; } /** * Finishes template parsing. - * @return array(prolog, epilog) */ public function finalize() { - // try close last block - $last = $this->getCompiler()->getMacroNode(); - if ($last && $last->name === 'block') { - $this->getCompiler()->closeMacro($last->name); + $compiler = $this->getCompiler(); + $functions = []; + foreach ($this->namedBlocks as $name => $code) { + $compiler->addMethod( + $functions[$name] = $this->generateMethodName($name), + '?>' . $compiler->expandTokens($code) . 'namedBlocks) { - foreach ($this->namedBlocks as $name => $code) { - $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name); - $snippet = $name[0] === '_'; - $prolog[] = "//\n// block $name\n//\n" - . "if (!function_exists(\$_b->blocks[" . var_export($name, TRUE) . "][] = '$func')) { " - . "function $func(\$_b, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v" - . ($snippet ? '; $_control->redrawControl(' . var_export((string) substr($name, 1), TRUE) . ', FALSE)' : '') - . "\n?>$codeaddProperty('blocks', $functions); + $compiler->addProperty('blockTypes', $this->blockTypes); } - if ($this->namedBlocks || $this->extends) { - $prolog[] = '// template extending'; - - $prolog[] = '$_l->extends = ' - . ($this->extends ? $this->extends : 'empty($_g->extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL') - . '; $_g->extended = TRUE;'; - - $prolog[] = 'if ($_l->extends) { ob_start(function () {});}'; - if (!$this->namedBlocks) { - $epilog[] = 'if ($_l->extends) { ob_end_clean(); return $template->renderChildTemplate($_l->extends, get_defined_vars());}'; - } - } - - return array(implode("\n\n", $prolog), implode("\n", $epilog)); + return [ + ($this->extends === NULL ? '' : '$this->parentName = ' . $this->extends . ';') . implode($this->imports) + ]; } @@ -100,10 +94,11 @@ /** - * {include #block} + * {include block} */ public function macroInclude(MacroNode $node, PhpWriter $writer) { + $node->replaced = FALSE; $destination = $node->tokenizer->fetchWord(); // destination [,] [params] if (!preg_match('~#|[\w-]+\z~A', $destination)) { return FALSE; @@ -119,78 +114,99 @@ $destination = $item->data->name; } - $name = strpos($destination, '$') === FALSE ? var_export($destination, TRUE) : $destination; - if (isset($this->namedBlocks[$destination]) && !$parent) { - $cmd = "call_user_func(reset(\$_b->blocks[$name]), \$_b, %node.array? + get_defined_vars())"; - } else { - $cmd = 'Latte\Macros\BlockMacrosRuntime::callBlock' . ($parent ? 'Parent' : '') . "(\$_b, $name, %node.array? + " . ($parent ? 'get_defined_vars' : '$template->getParameters') . '())'; + $noEscape = Helpers::removeFilter($node->modifiers, 'noescape'); + if (!$noEscape && Helpers::removeFilter($node->modifiers, 'escape')) { + trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.'); } - - if ($node->modifiers) { - return $writer->write("ob_start(function () {}); $cmd; echo %modify(ob_get_clean())"); - } else { - return $writer->write($cmd); + if ($node->modifiers && !$noEscape) { + $node->modifiers .= '|escape'; } + return $writer->write( + '$this->renderBlock' . ($parent ? 'Parent' : '') . '(' + . (strpos($destination, '$') === FALSE ? var_export($destination, TRUE) : $destination) + . ', %node.array? + ' + . (isset($this->namedBlocks[$destination]) || $parent ? 'get_defined_vars()' : '$this->params') + . ($node->modifiers + ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' + : ($noEscape || $parent ? '' : ', ' . var_export(implode($node->context), TRUE))) + . ');' + ); } /** * {includeblock "file"} + * @deprecated */ public function macroIncludeBlock(MacroNode $node, PhpWriter $writer) { + trigger_error('Macro {includeblock} is deprecated, use similar macro {import}.', E_USER_DEPRECATED); + $node->replaced = FALSE; if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } return $writer->write( - 'ob_start(function () {}); $_g->includingBlock = isset($_g->includingBlock) ? ++$_g->includingBlock : 1; $_b->templates[%var]->renderChildTemplate(%node.word, %node.array? + get_defined_vars()); $_g->includingBlock--; echo rtrim(ob_get_clean())', - $this->getCompiler()->getTemplateId() + 'ob_start(function () {}); $this->createTemplate(%node.word, %node.array? + get_defined_vars(), "includeblock")->renderToContentType(%var); echo rtrim(ob_get_clean());', + implode($node->context) ); } /** - * {extends auto | none | $var | "file"} + * {import "file"} */ - public function macroExtends(MacroNode $node, PhpWriter $writer) + public function macroImport(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); - } - if (!$node->args) { - throw new CompileException("Missing destination in {{$node->name}}"); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } - if (!empty($node->parentNode)) { - throw new CompileException("{{$node->name}} must be placed outside any macro."); - } - if ($this->extends !== NULL) { - throw new CompileException("Multiple {{$node->name}} declarations are not allowed."); + $destination = $node->tokenizer->fetchWord(); + $this->checkExtraArgs($node); + $code = $writer->write('$this->createTemplate(%word, $this->params, "import")->render();', $destination); + if ($this->getCompiler()->isInHead()) { + $this->imports[] = $code; + } else { + return $code; } - if ($node->args === 'none') { + } + + + /** + * {extends none | $var | "file"} + */ + public function macroExtends(MacroNode $node, PhpWriter $writer) + { + $notation = $node->getNotation(); + if ($node->modifiers) { + throw new CompileException("Modifiers are not allowed in $notation"); + } elseif (!$node->args) { + throw new CompileException("Missing destination in $notation"); + } elseif ($node->parentNode) { + throw new CompileException("$notation must be placed outside any macro."); + } elseif ($this->extends !== NULL) { + throw new CompileException("Multiple $notation declarations are not allowed."); + } elseif ($node->args === 'none') { $this->extends = 'FALSE'; - } elseif ($node->args === 'auto') { - $this->extends = '$_presenter->findLayoutTemplateFile()'; } else { $this->extends = $writer->write('%node.word%node.args'); } - return; + if (!$this->getCompiler()->isInHead()) { + trigger_error("$notation must be placed in template head.", E_USER_WARNING); + } } /** - * {block [[#]name]} + * {block [name]} * {snippet [name [,]] [tag]} * {snippetArea [name]} - * {define [#]name} + * {define name} */ public function macroBlock(MacroNode $node, PhpWriter $writer) { $name = $node->tokenizer->fetchWord(); - if ($node->name === '#') { - trigger_error('Shortcut {#block} is deprecated.', E_USER_DEPRECATED); - - } elseif ($node->name === 'block' && $name === FALSE) { // anonymous block + if ($node->name === 'block' && $name === FALSE) { // anonymous block return $node->modifiers === '' ? '' : 'ob_start(function () {})'; } @@ -208,25 +224,31 @@ } $parent->data->dynamic = TRUE; $node->data->leave = TRUE; - $node->closingCode = "dynSnippets[\$_l->dynSnippetId] = ob_get_flush() ?>"; + $node->closingCode = "global->snippetDriver->leave(); ?>"; + $enterCode = '$this->global->snippetDriver->enter(' . $writer->formatWord($name) . ', "' . SnippetDriver::TYPE_DYNAMIC . '");'; if ($node->prefix) { - $node->attrCode = $writer->write("dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)})) . '\"' ?>"); - return $writer->write('ob_start()'); + $node->attrCode = $writer->write("global->snippetDriver->getHtmlId({$writer->formatWord($name)})) . '\"' ?>"); + return $writer->write($enterCode); } $tag = trim($node->tokenizer->fetchWord(), '<>'); + if ($tag) { + trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED); + } $tag = $tag ? $tag : 'div'; $node->closingCode .= "\n$tag>"; - return $writer->write("?>\n<$tag id=\"dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)}) ?>\">checkExtraArgs($node); + return $writer->write("?>\n<$tag id=\"global->snippetDriver->getHtmlId({$writer->formatWord($name)})) ?>\">data->leave = TRUE; + $node->data->func = $this->generateMethodName($name); $fname = $writer->formatWord($name); - $node->closingCode = 'name === 'define' ? '' : "call_user_func(reset(\$_b->blocks[$fname]), \$_b, get_defined_vars())") . ' ?>'; - $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name); - return "\n\n//\n// block $name\n//\n" - . "if (!function_exists(\$_b->blocks[$fname]['{$this->getCompiler()->getTemplateId()}'] = '$func')) { " - . "function $func(\$_b, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v"; + $node->closingCode = 'name === 'define' ? '' : "\$this->renderBlock($fname, get_defined_vars());") . ' ?>'; + $blockType = var_export(implode($node->context), TRUE); + $this->checkExtraArgs($node); + return "\$this->checkBlockContentType($blockType, $fname);" + . "\$this->blockQueue[$fname][] = [\$this, '{$node->data->func}'];"; } } @@ -241,31 +263,58 @@ if (isset($this->namedBlocks[$name])) { throw new CompileException("Cannot redeclare static {$node->name} '$name'"); } - - $prolog = $this->namedBlocks ? '' : "if (\$_l->extends) { ob_end_clean(); return \$template->renderChildTemplate(\$_l->extends, get_defined_vars()); }\n"; + $extendsCheck = $this->namedBlocks ? '' : 'if ($this->getParentName()) return get_defined_vars();'; $this->namedBlocks[$name] = TRUE; - $include = 'call_user_func(reset($_b->blocks[%var]), $_b, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$template->getParameters()' : 'get_defined_vars()') . ')'; - if ($node->modifiers) { - $include = "ob_start(function () {}); $include; echo %modify(ob_get_clean())"; + if (Helpers::removeFilter($node->modifiers, 'escape')) { + trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.'); } + if (Helpers::startsWith($node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) { + $node->context[1] = ''; + $node->modifiers .= '|escape'; + } elseif ($node->modifiers) { + $node->modifiers .= '|escape'; + } + $this->blockTypes[$name] = implode($node->context); + + $include = '$this->renderBlock(%var, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$this->params' : 'get_defined_vars()') + . ($node->modifiers ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' : '') . ')'; if ($node->name === 'snippet') { if ($node->prefix) { - $node->attrCode = $writer->write('getSnippetId(%var) . \'"\' ?>', (string) substr($name, 1)); - return $writer->write($prolog . $include, $name); + if (isset($node->htmlNode->macroAttrs['foreach'])) { + trigger_error('Combination of n:snippet with n:foreach is invalid, use n:inner-foreach.', E_USER_WARNING); + } + $node->attrCode = $writer->write('global->snippetDriver->getHtmlId(%var)) . \'"\' ?>', (string) substr($name, 1)); + return $writer->write($include, $name); } $tag = trim($node->tokenizer->fetchWord(), '<>'); + if ($tag) { + trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED); + } $tag = $tag ? $tag : 'div'; - return $writer->write("$prolog ?>\n<$tag id=\"getSnippetId(%var) ?>\">\n$tag>checkExtraArgs($node); + return $writer->write("?>\n<$tag id=\"global->snippetDriver->getHtmlId(%var)) ?>\">\n$tag>name === 'define') { - return $prolog; + $tokens = $node->tokenizer; + $args = []; + while ($tokens->isNext()) { + $args[] = $tokens->expectNextValue($tokens::T_VARIABLE); + if ($tokens->isNext()) { + $tokens->expectNextValue(','); + } + } + if ($args) { + $node->data->args = 'list(' . implode(', ', $args) . ') = $_args + [' . str_repeat('NULL, ', count($args)) . '];'; + } + return $extendsCheck; } else { // block, snippetArea - return $writer->write($prolog . $include, $name); + $this->checkExtraArgs($node); + return $writer->write($extendsCheck . $include, $name); } } @@ -279,55 +328,80 @@ public function macroBlockEnd(MacroNode $node, PhpWriter $writer) { if (isset($node->data->name)) { // block, snippet, define - if ($node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE // n:snippet -> n:inner-snippet - && preg_match('#^.*? n:\w+>\n?#s', $node->content, $m1) && preg_match('#[ \t]*<[^<]+\z#s', $node->content, $m2) - ) { - $node->openingCode = $m1[0] . $node->openingCode; - $node->content = substr($node->content, strlen($m1[0]), -strlen($m2[0])); - $node->closingCode .= $m2[0]; + if ($asInner = $node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE) { + $node->content = $node->innerContent; } + if (($node->name === 'snippet' || $node->name === 'snippetArea') && strpos($node->data->name, '$') === FALSE) { + $type = $node->name === 'snippet' ? SnippetDriver::TYPE_STATIC : SnippetDriver::TYPE_AREA; + $node->content = 'global->snippetDriver->enter(' + . $writer->formatWord(substr($node->data->name, 1)) + . ', "' . $type . '"); ?>' + . preg_replace('#(?<=\n)[ \t]+\z#', '', $node->content) . 'global->snippetDriver->leave(); ?>'; + } if (empty($node->data->leave)) { - if ($node->name === 'snippetArea' && empty($node->data->dynamic)) { - $node->content = "snippetMode = isset(\$_snippetMode) && \$_snippetMode; ?>{$node->content}snippetMode = FALSE; ?>"; - } - if (!empty($node->data->dynamic)) { - $node->content .= 'dynSnippets)) return $_l->dynSnippets; ?>'; - } - if ($node->name === 'snippetArea') { - $node->content .= ''; + if (preg_match('#\$|n:#', $node->content)) { + $node->content = 'data->args) ? $node->data->args : 'extract($_args);') . ' ?>' . $node->content; } $this->namedBlocks[$node->data->name] = $tmp = preg_replace('#^\n+|(?<=\n)[ \t]+\z#', '', $node->content); $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp)); $node->openingCode = ''; + + } elseif (isset($node->data->func)) { + $node->content = rtrim($node->content, " \t"); + $this->getCompiler()->addMethod( + $node->data->func, + $this->getCompiler()->expandTokens("extract(\$_args);\n?>$node->contentcontent = ''; } + if ($asInner) { // n:snippet -> n:inner-snippet + $node->innerContent = $node->openingCode . $node->content . $node->closingCode; + $node->closingCode = $node->openingCode = ''; + } + return ' '; // consume next new line + } elseif ($node->modifiers) { // anonymous block with modifier - return $writer->write('echo %modify(ob_get_clean())'); + $node->modifiers .= '|escape'; + return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent(ob_get_clean());', $node->context[0]); } } /** - * {ifset #block} - * {elseifset #block} + * {ifset block} + * {elseifset block} */ public function macroIfset(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } if (!preg_match('~#|[\w-]+\z~A', $node->args)) { return FALSE; } - $list = array(); + $list = []; while (($name = $node->tokenizer->fetchWord()) !== FALSE) { $list[] = preg_match('~#|[\w-]+\z~A', $name) - ? '$_b->blocks["' . ltrim($name, '#') . '"]' + ? '$this->blockQueue["' . ltrim($name, '#') . '"]' : $writer->formatArgs(new Latte\MacroTokens($name)); } return ($node->name === 'elseifset' ? '} else' : '') . 'if (isset(' . implode(', ', $list) . ')) {'; } + + private function generateMethodName($blockName) + { + $clean = trim(preg_replace('#\W+#', '_', $blockName), '_'); + $name = 'block' . ucfirst($clean); + $methods = array_keys($this->getCompiler()->getMethods()); + if (!$clean || in_array(strtolower($name), array_map('strtolower', $methods))) { + $name .= '_' . substr(md5($blockName), 0, 5); + } + return $name; + } + } diff -Nru php-nette-2.3.10/Nette/Latte/Macros/BlockMacrosRuntime.php php-nette-2.4-20160731/Nette/Latte/Macros/BlockMacrosRuntime.php --- php-nette-2.3.10/Nette/Latte/Macros/BlockMacrosRuntime.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Macros/BlockMacrosRuntime.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -blocks[$name])) { - $hint = isset($context->blocks) && ($t = Latte\Helpers::getSuggestion(array_keys($context->blocks), $name)) ? ", did you mean '$t'?" : '.'; - throw new RuntimeException("Cannot include undefined block '$name'$hint"); - } - $block = reset($context->blocks[$name]); - $block($context, $params); - } - - - /** - * Calls parent block. - * @return void - */ - public static function callBlockParent(\stdClass $context, $name, array $params) - { - if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) { - throw new RuntimeException("Cannot include undefined parent block '$name'."); - } - $block($context, $params); - prev($context->blocks[$name]); - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Macros/CoreMacros.php php-nette-2.4-20160731/Nette/Latte/Macros/CoreMacros.php --- php-nette-2.3.10/Nette/Latte/Macros/CoreMacros.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Macros/CoreMacros.php 2016-07-31 17:46:38.000000000 +0000 @@ -9,6 +9,8 @@ use Latte; use Latte\CompileException; +use Latte\Engine; +use Latte\Helpers; use Latte\MacroNode; use Latte\PhpWriter; @@ -22,10 +24,11 @@ * - {foreach ?} ... {/foreach} * - {$variable} with escaping * - {=expression} echo with escaping - * - {?expression} evaluate PHP statement + * - {php expression} evaluate PHP statement * - {_expression} echo translation with escaping * - {attr ?} HTML element attributes * - {capture ?} ... {/capture} capture block to parameter + * - {spaceless} ... {/spaceless} compress whitespaces * - {var var => value} set template parameter * - {default var => value} set default template parameter * - {dump $var} @@ -36,51 +39,64 @@ */ class CoreMacros extends MacroSet { + /** @var array */ + private $overwrittenVars; public static function install(Latte\Compiler $compiler) { $me = new static($compiler); - $me->addMacro('if', array($me, 'macroIf'), array($me, 'macroEndIf')); + $me->addMacro('if', [$me, 'macroIf'], [$me, 'macroEndIf']); $me->addMacro('elseif', '} elseif (%node.args) {'); - $me->addMacro('else', array($me, 'macroElse')); + $me->addMacro('else', [$me, 'macroElse']); $me->addMacro('ifset', 'if (isset(%node.args)) {', '}'); $me->addMacro('elseifset', '} elseif (isset(%node.args)) {'); - $me->addMacro('ifcontent', array($me, 'macroIfContent'), array($me, 'macroEndIfContent')); + $me->addMacro('ifcontent', [$me, 'macroIfContent'], [$me, 'macroEndIfContent']); - $me->addMacro('switch', '$_l->switch[] = (%node.args); if (FALSE) {', '} array_pop($_l->switch)'); - $me->addMacro('case', '} elseif (end($_l->switch) === (%node.args)) {'); + $me->addMacro('switch', '$this->global->switch[] = (%node.args); if (FALSE) {', '} array_pop($this->global->switch)'); + $me->addMacro('case', '} elseif (end($this->global->switch) === (%node.args)) {'); - $me->addMacro('foreach', '', array($me, 'macroEndForeach')); + $me->addMacro('foreach', '', [$me, 'macroEndForeach']); $me->addMacro('for', 'for (%node.args) {', '}'); - $me->addMacro('while', 'while (%node.args) {', '}'); - $me->addMacro('continueIf', array($me, 'macroBreakContinueIf')); - $me->addMacro('breakIf', array($me, 'macroBreakContinueIf')); + $me->addMacro('while', [$me, 'macroWhile'], [$me, 'macroEndWhile']); + $me->addMacro('continueIf', [$me, 'macroBreakContinueIf']); + $me->addMacro('breakIf', [$me, 'macroBreakContinueIf']); $me->addMacro('first', 'if ($iterator->isFirst(%node.args)) {', '}'); $me->addMacro('last', 'if ($iterator->isLast(%node.args)) {', '}'); $me->addMacro('sep', 'if (!$iterator->isLast(%node.args)) {', '}'); - $me->addMacro('var', array($me, 'macroVar')); - $me->addMacro('default', array($me, 'macroVar')); - $me->addMacro('dump', array($me, 'macroDump')); - $me->addMacro('debugbreak', array($me, 'macroDebugbreak')); + $me->addMacro('var', [$me, 'macroVar']); + $me->addMacro('default', [$me, 'macroVar']); + $me->addMacro('dump', [$me, 'macroDump']); + $me->addMacro('debugbreak', [$me, 'macroDebugbreak']); $me->addMacro('l', '?>{addMacro('r', '?>}addMacro('_', array($me, 'macroTranslate'), array($me, 'macroTranslate')); - $me->addMacro('=', array($me, 'macroExpr')); - $me->addMacro('?', array($me, 'macroExpr')); - - $me->addMacro('capture', array($me, 'macroCapture'), array($me, 'macroCaptureEnd')); - $me->addMacro('include', array($me, 'macroInclude')); - $me->addMacro('use', array($me, 'macroUse')); - $me->addMacro('contentType', array($me, 'macroContentType')); - $me->addMacro('status', array($me, 'macroStatus')); - $me->addMacro('php', array($me, 'macroExpr')); + $me->addMacro('_', [$me, 'macroTranslate'], [$me, 'macroTranslate']); + $me->addMacro('=', [$me, 'macroExpr']); + $me->addMacro('?', [$me, 'macroExpr']); - $me->addMacro('class', NULL, NULL, array($me, 'macroClass')); - $me->addMacro('attr', NULL, NULL, array($me, 'macroAttr')); + $me->addMacro('capture', [$me, 'macroCapture'], [$me, 'macroCaptureEnd']); + $me->addMacro('spaceless', [$me, 'macroSpaceless'], [$me, 'macroSpaceless']); + $me->addMacro('include', [$me, 'macroInclude']); + $me->addMacro('use', [$me, 'macroUse']); + $me->addMacro('contentType', [$me, 'macroContentType'], NULL, NULL, self::ALLOWED_IN_HEAD); + $me->addMacro('status', [$me, 'macroStatus']); + $me->addMacro('php', [$me, 'macroExpr']); + + $me->addMacro('class', NULL, NULL, [$me, 'macroClass']); + $me->addMacro('attr', NULL, NULL, [$me, 'macroAttr']); + } + + + /** + * Initializes before template parsing. + * @return void + */ + public function initialize() + { + $this->overwrittenVars = []; } @@ -90,10 +106,13 @@ */ public function finalize() { - return array('list($_b, $_g, $_l) = $template->initialize(' - . var_export($this->getCompiler()->getTemplateId(), TRUE) . ', ' - . var_export($this->getCompiler()->getContentType(), TRUE) - . ')'); + $code = ''; + foreach ($this->overwrittenVars as $var => $lines) { + $s = var_export($var, TRUE); + $code .= 'if (isset($this->params[' . var_export($var, TRUE) + . "])) trigger_error('Variable $" . addcslashes($var, "'") . ' overwritten in foreach on line ' . implode(', ', $lines) . "'); "; + } + return [$code]; } @@ -106,13 +125,13 @@ public function macroIf(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } if ($node->data->capture = ($node->args === '')) { return 'ob_start(function () {})'; } if ($node->prefix === $node::PREFIX_TAG) { - return $writer->write($node->htmlNode->closing ? 'if (array_pop($_l->ifs)) {' : 'if ($_l->ifs[] = (%node.args)) {'); + return $writer->write($node->htmlNode->closing ? 'if (array_pop($this->global->ifs)) {' : 'if ($this->global->ifs[] = (%node.args)) {'); } return $writer->write('if (%node.args) {'); } @@ -130,7 +149,7 @@ return $writer->write('if (%node.args) ' . (isset($node->data->else) ? '{ ob_end_clean(); echo ob_get_clean(); }' : 'echo ob_get_clean();') . ' else ' - . (isset($node->data->else) ? '{ $_l->else = ob_get_clean(); ob_end_clean(); echo $_l->else; }' : 'ob_end_clean();') + . (isset($node->data->else) ? '{ $this->global->else = ob_get_clean(); ob_end_clean(); echo $this->global->else; }' : 'ob_end_clean();') ); } return '}'; @@ -143,10 +162,10 @@ public function macroElse(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } elseif ($node->args) { - $hint = substr($node->args, 0, 2) === 'if' ? ', did you mean {elseif}?' : ''; - trigger_error("Arguments are not allowed in {{$node->name}}$hint", E_USER_WARNING); + $hint = Helpers::startsWith($node->args, 'if') ? ', did you mean {elseif}?' : ''; + throw new CompileException('Arguments are not allowed in ' . $node->getNotation() . $hint); } $ifNode = $node->parentNode; if ($ifNode && $ifNode->name === 'if' && $ifNode->data->capture) { @@ -165,13 +184,9 @@ */ public function macroIfContent(MacroNode $node, PhpWriter $writer) { - if (!$node->prefix) { - throw new CompileException("Unknown macro {{$node->name}}, use n:{$node->name} attribute."); - } elseif ($node->prefix !== MacroNode::PREFIX_NONE) { - throw new CompileException("Unknown attribute n:{$node->prefix}-{$node->name}, use n:{$node->name} attribute."); + if (!$node->prefix || $node->prefix !== MacroNode::PREFIX_NONE) { + throw new CompileException('Unknown ' . $node->getNotation() . ", use n:{$node->name} attribute."); } - - return $writer->write('ob_start(function () {})'); } @@ -180,13 +195,9 @@ */ public function macroEndIfContent(MacroNode $node, PhpWriter $writer) { - preg_match('#(^.*?>)(.*)(<.*\z)#s', $node->content, $parts); - $node->content = $parts[1] - . '' - . $parts[2] - . 'ifcontent = ob_get_flush() ?>' - . $parts[3]; - return 'if (rtrim($_l->ifcontent) === "") ob_end_clean(); else echo ob_get_clean()'; + $node->openingCode = ''; + $node->innerContent = '' . $node->innerContent . 'global->ifcontent = ob_get_flush(); ?>'; + $node->closingCode = 'global->ifcontent) === "") ob_end_clean(); else echo ob_get_clean(); ?>'; } @@ -196,13 +207,18 @@ public function macroTranslate(MacroNode $node, PhpWriter $writer) { if ($node->closing) { - return $writer->write('echo %modify($template->translate(ob_get_clean()))'); + if (strpos($node->content, 'content, TRUE); + $node->content = ''; + } else { + $node->openingCode = '' . $node->openingCode; + $value = 'ob_get_clean()'; + } - } elseif ($node->isEmpty = ($node->args !== '')) { - return $writer->write('echo %modify($template->translate(%node.args))'); + return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent($this->filters->filterContent("translate", $_fi, %raw))', $node->context[0], $value); - } else { - return 'ob_start(function () {})'; + } elseif ($node->empty = ($node->args !== '')) { + return $writer->write('echo %modify(call_user_func($this->filters->translate, %node.args))'); } } @@ -212,14 +228,21 @@ */ public function macroInclude(MacroNode $node, PhpWriter $writer) { - $code = $writer->write('$_b->templates[%var]->renderChildTemplate(%node.word, %node.array? + $template->getParameters())', - $this->getCompiler()->getTemplateId()); - - if ($node->modifiers) { - return $writer->write('ob_start(function () {}); %raw; echo %modify(ob_get_clean())', $code); - } else { - return $code; + $node->replaced = FALSE; + $noEscape = Helpers::removeFilter($node->modifiers, 'noescape'); + if (!$noEscape && Helpers::removeFilter($node->modifiers, 'escape')) { + trigger_error('Macro {include} provides auto-escaping, remove |escape.'); + } + if ($node->modifiers && !$noEscape) { + $node->modifiers .= '|escape'; } + return $writer->write( + '/* line ' . $node->startLine . ' */ + $this->createTemplate(%node.word, %node.array? + $this->params, "include")->renderToContentType(%raw);', + $node->modifiers + ? $writer->write('function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }') + : var_export($noEscape ? NULL : implode($node->context), TRUE) + ); } @@ -228,10 +251,11 @@ */ public function macroUse(MacroNode $node, PhpWriter $writer) { + trigger_error('Macro {use} is deprecated.', E_USER_DEPRECATED); if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } - call_user_func(Latte\Helpers::checkCallback(array($node->tokenizer->fetchWord(), 'install')), $this->getCompiler()) + call_user_func(Helpers::checkCallback([$node->tokenizer->fetchWord(), 'install']), $this->getCompiler()) ->initialize(); } @@ -242,9 +266,10 @@ public function macroCapture(MacroNode $node, PhpWriter $writer) { $variable = $node->tokenizer->fetchWord(); - if (substr($variable, 0, 1) !== '$') { + if (!Helpers::startsWith($variable, '$')) { throw new CompileException("Invalid capture block variable '$variable'"); } + $this->checkExtraArgs($node); $node->data->variable = $variable; return 'ob_start(function () {})'; } @@ -255,7 +280,55 @@ */ public function macroCaptureEnd(MacroNode $node, PhpWriter $writer) { - return $node->data->variable . $writer->write(' = %modify(ob_get_clean())'); + $body = in_array($node->context[0], [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], TRUE) + ? "ob_get_length() ? new LR\\Html(ob_get_clean()) : ob_get_clean()" + : 'ob_get_clean()'; + return $writer->write("\$_fi = new LR\\FilterInfo(%var); %raw = %modifyContent($body);", $node->context[0], $node->data->variable); + } + + + /** + * {spaceless} ... {/spaceless} + */ + public function macroSpaceless(MacroNode $node, PhpWriter $writer) + { + if ($node->modifiers || $node->args) { + throw new CompileException('Modifiers and arguments are not allowed in ' . $node->getNotation()); + } + $node->openingCode = in_array($node->context[0], [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], TRUE) + ? '' + : ""; + $node->closingCode = ''; + } + + + /** + * {while ...} + */ + public function macroWhile(MacroNode $node, PhpWriter $writer) + { + if ($node->modifiers) { + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); + } + if ($node->data->do = ($node->args === '')) { + return 'do {'; + } + return $writer->write('while (%node.args) {'); + } + + + /** + * {/while ...} + */ + public function macroEndWhile(MacroNode $node, PhpWriter $writer) + { + if ($node->data->do) { + if ($node->args === '') { + throw new CompileException('Missing condition in {while} macro.'); + } + return $writer->write('} while (%node.args);'); + } + return '}'; } @@ -264,15 +337,25 @@ */ public function macroEndForeach(MacroNode $node, PhpWriter $writer) { - if ($node->modifiers && $node->modifiers !== '|noiterator') { - trigger_error('Only modifier |noiterator is allowed here.', E_USER_WARNING); + $noCheck = Helpers::removeFilter($node->modifiers, 'nocheck'); + $noIterator = Helpers::removeFilter($node->modifiers, 'noiterator'); + if ($node->modifiers) { + throw new CompileException('Only modifiers |noiterator and |nocheck are allowed here.'); + } + $node->openingCode = 'formatArgs(); + if (!$noCheck) { + preg_match('#.+\s+as\s*\$(\w+)(?:\s*=>\s*\$(\w+))?#i', $args, $m); + for ($i = 1; $i < count($m); $i++) { + $this->overwrittenVars[$m[$i]][] = $node->startLine; + } } - if ($node->modifiers !== '|noiterator' && preg_match('#\W(\$iterator|include|require|get_defined_vars)\W#', $this->getCompiler()->expandTokens($node->content))) { - $node->openingCode = 'its[] = new Latte\Runtime\CachingIterator(' - . preg_replace('#(.*)\s+as\s+#i', '$1) as ', $writer->formatArgs(), 1) . ') { ?>'; - $node->closingCode = 'its); $iterator = end($_l->its) ?>'; + if (!$noIterator && preg_match('#\W(\$iterator|include|require|get_defined_vars)\W#', $this->getCompiler()->expandTokens($node->content))) { + $node->openingCode .= 'foreach ($iterator = $this->global->its[] = new LR\CachingIterator(' + . preg_replace('#(.*)\s+as\s+#i', '$1) as ', $args, 1) . ') { ?>'; + $node->closingCode = 'global->its); $iterator = end($this->global->its); ?>'; } else { - $node->openingCode = 'formatArgs() . ') { ?>'; + $node->openingCode .= 'foreach (' . $args . ') { ?>'; $node->closingCode = ''; } } @@ -285,13 +368,13 @@ public function macroBreakContinueIf(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } $cmd = str_replace('If', '', $node->name); if ($node->parentNode && $node->parentNode->prefix === $node::PREFIX_NONE) { return $writer->write("if (%node.args) { echo \"{$node->parentNode->htmlNode->name}>\\n\"; $cmd; }"); } - return $writer->write("if (%node.args) $cmd"); + return $writer->write("if (%node.args) $cmd;"); } @@ -303,7 +386,7 @@ if (isset($node->htmlNode->attrs['class'])) { throw new CompileException('It is not possible to combine class with n:class.'); } - return $writer->write('if ($_l->tmp = array_filter(%node.array)) echo \' class="\', %escape(implode(" ", array_unique($_l->tmp))), \'"\''); + return $writer->write('if ($_tmp = array_filter(%node.array)) echo \' class="\', %escape(implode(" ", array_unique($_tmp))), \'"\''); } @@ -312,7 +395,7 @@ */ public function macroAttr(MacroNode $node, PhpWriter $writer) { - return $writer->write('echo Latte\Runtime\Filters::htmlAttributes(%node.array)'); + return $writer->write('echo LR\Filters::htmlAttributes(%node.array);'); } @@ -322,11 +405,11 @@ public function macroDump(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } $args = $writer->formatArgs(); return $writer->write( - 'Tracy\Debugger::barDump(' . ($args ? "($args)" : 'get_defined_vars()'). ', %var)', + 'Tracy\Debugger::barDump(' . ($args ? "($args)" : 'get_defined_vars()'). ', %var);', $args ?: 'variables' ); } @@ -338,10 +421,11 @@ public function macroDebugbreak(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); + } + if (function_exists($func = 'debugbreak') || function_exists($func = 'xdebug_break')) { + return $writer->write($node->args == NULL ? "$func()" : "if (%node.args) $func();"); } - return $writer->write(($node->args == NULL ? '' : 'if (!(%node.args)); else') - . 'if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break()'); } @@ -352,7 +436,7 @@ public function macroVar(MacroNode $node, PhpWriter $writer) { if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } if ($node->args === '' && $node->parentNode && $node->parentNode->name === 'switch') { return '} else {'; @@ -362,7 +446,7 @@ $tokens = $writer->preprocess(); $res = new Latte\MacroTokens; while ($tokens->nextToken()) { - if ($var && $tokens->isCurrent(Latte\MacroTokens::T_SYMBOL, Latte\MacroTokens::T_VARIABLE)) { + if ($var && $tokens->isCurrent($tokens::T_SYMBOL, $tokens::T_VARIABLE)) { if ($node->name === 'default') { $res->append("'" . ltrim($tokens->currentValue(), '$') . "'"); } else { @@ -381,7 +465,7 @@ $res->append($node->name === 'default' ? ',' : ';'); $var = TRUE; - } elseif ($var === NULL && $node->name === 'default' && !$tokens->isCurrent(Latte\MacroTokens::T_WHITESPACE)) { + } elseif ($var === NULL && $node->name === 'default' && !$tokens->isCurrent($tokens::T_WHITESPACE)) { throw new CompileException("Unexpected '{$tokens->currentValue()}' in {default $node->args}"); } else { @@ -391,18 +475,24 @@ if ($var === NULL) { $res->append($node->name === 'default' ? '=>NULL' : '=NULL'); } - $out = $writer->quoteFilter($res)->joinAll(); - return $node->name === 'default' ? "extract(array($out), EXTR_SKIP)" : $out; + $out = $writer->quotingPass($res)->joinAll(); + return $node->name === 'default' ? "extract([$out], EXTR_SKIP)" : "$out;"; } /** * {= ...} - * {? ...} + * {php ...} */ public function macroExpr(MacroNode $node, PhpWriter $writer) { - return $writer->write(($node->name === '=' ? 'echo ' : '') . '%modify(%node.args)'); + if ($node->name === '?') { + trigger_error('Macro {? ...} is deprecated, use {php ...}.', E_USER_DEPRECATED); + } + return $writer->write($node->name === '=' + ? "echo %modify(%node.args) /* line $node->startLine */" + : '%modify(%node.args);' + ); } @@ -411,34 +501,33 @@ */ public function macroContentType(MacroNode $node, PhpWriter $writer) { - if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + if (!$this->getCompiler()->isInHead() + && !($node->htmlNode && strtolower($node->htmlNode->name) === 'script' && strpos($node->args, 'html') !== FALSE) + ) { + throw new CompileException($node->getNotation() . ' is allowed only in template header.'); } - if (strpos($node->args, 'xhtml') !== FALSE) { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XHTML); - + $compiler = $this->getCompiler(); + if ($node->modifiers) { + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); + } elseif (strpos($node->args, 'xhtml') !== FALSE) { + $type = $compiler::CONTENT_XHTML; } elseif (strpos($node->args, 'html') !== FALSE) { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_HTML); - + $type = $compiler::CONTENT_HTML; } elseif (strpos($node->args, 'xml') !== FALSE) { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XML); - + $type = $compiler::CONTENT_XML; } elseif (strpos($node->args, 'javascript') !== FALSE) { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_JS); - + $type = $compiler::CONTENT_JS; } elseif (strpos($node->args, 'css') !== FALSE) { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_CSS); - + $type = $compiler::CONTENT_CSS; } elseif (strpos($node->args, 'calendar') !== FALSE) { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_ICAL); - + $type = $compiler::CONTENT_ICAL; } else { - $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_TEXT); + $type = $compiler::CONTENT_TEXT; } + $compiler->setContentType($type); - // temporary solution - if (strpos($node->args, '/')) { - return $writer->write('header(%var)', "Content-Type: $node->args"); + if (strpos($node->args, '/') && !$node->htmlNode) { + return $writer->write('if (empty($this->global->coreCaptured) && in_array($this->getReferenceType(), ["extends", NULL], TRUE)) header(%var);', "Content-Type: $node->args"); } } @@ -448,11 +537,12 @@ */ public function macroStatus(MacroNode $node, PhpWriter $writer) { + trigger_error('Macro {status} is deprecated.', E_USER_DEPRECATED); if ($node->modifiers) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } return $writer->write((substr($node->args, -1) === '?' ? 'if (!headers_sent()) ' : '') . - 'header((isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : "HTTP/1.1") . " " . %0.var, TRUE, %0.var)', (int) $node->args + 'header((isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : "HTTP/1.1") . " " . %0.var, TRUE, %0.var);', (int) $node->args ); } diff -Nru php-nette-2.3.10/Nette/Latte/Macros/MacroSet.php php-nette-2.4-20160731/Nette/Latte/Macros/MacroSet.php --- php-nette-2.3.10/Nette/Latte/Macros/MacroSet.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Macros/MacroSet.php 2016-07-31 17:46:38.000000000 +0000 @@ -8,14 +8,17 @@ namespace Latte\Macros; use Latte; +use Latte\CompileException; use Latte\MacroNode; /** * Base IMacro implementation. Allows add multiple macros. */ -class MacroSet extends Latte\Object implements Latte\IMacro +class MacroSet implements Latte\IMacro { + use Latte\Strict; + /** @var Latte\Compiler */ private $compiler; @@ -29,19 +32,19 @@ } - public function addMacro($name, $begin, $end = NULL, $attr = NULL) + public function addMacro($name, $begin, $end = NULL, $attr = NULL, $flags = NULL) { if (!$begin && !$end && !$attr) { throw new \InvalidArgumentException("At least one argument must be specified for macro '$name'."); } - foreach (array($begin, $end, $attr) as $arg) { + foreach ([$begin, $end, $attr] as $arg) { if ($arg && !is_string($arg)) { Latte\Helpers::checkCallback($arg); } } - $this->macros[$name] = array($begin, $end, $attr); - $this->compiler->addMacro($name, $this); + $this->macros[$name] = [$begin, $end, $attr]; + $this->compiler->addMacro($name, $this, $flags); return $this; } @@ -71,14 +74,14 @@ public function nodeOpened(MacroNode $node) { list($begin, $end, $attr) = $this->macros[$node->name]; - $node->isEmpty = !$end; + $node->empty = !$end; if ($node->modifiers && (!$begin || (is_string($begin) && strpos($begin, '%modify') === FALSE)) && (!$end || (is_string($end) && strpos($end, '%modify') === FALSE)) && (!$attr || (is_string($attr) && strpos($attr, '%modify') === FALSE)) ) { - trigger_error("Modifiers are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); } if ($node->args @@ -86,25 +89,25 @@ && (!$end || (is_string($end) && strpos($end, '%node') === FALSE)) && (!$attr || (is_string($attr) && strpos($attr, '%node') === FALSE)) ) { - trigger_error("Arguments are not allowed in {{$node->name}}", E_USER_WARNING); + throw new CompileException('Arguments are not allowed in ' . $node->getNotation()); } if ($attr && $node->prefix === $node::PREFIX_NONE) { - $node->isEmpty = TRUE; - $this->compiler->setContext(Latte\Compiler::CONTEXT_DOUBLE_QUOTED_ATTR); + $node->empty = TRUE; + $node->context[1] = Latte\Compiler::CONTEXT_HTML_ATTRIBUTE; $res = $this->compile($node, $attr); if ($res === FALSE) { return FALSE; } elseif (!$node->attrCode) { $node->attrCode = ""; } - $this->compiler->setContext(NULL); + $node->context[1] = Latte\Compiler::CONTEXT_HTML_TEXT; } elseif ($begin) { $res = $this->compile($node, $begin); - if ($res === FALSE || ($node->isEmpty && $node->prefix)) { + if ($res === FALSE || ($node->empty && $node->prefix)) { return FALSE; - } elseif (!$node->openingCode) { + } elseif (!$node->openingCode && is_string($res) && $res !== '') { $node->openingCode = ""; } @@ -122,7 +125,7 @@ { if (isset($this->macros[$node->name][1])) { $res = $this->compile($node, $this->macros[$node->name][1]); - if (!$node->closingCode) { + if (!$node->closingCode && is_string($res) && $res !== '') { $node->closingCode = ""; } } @@ -136,12 +139,10 @@ private function compile(MacroNode $node, $def) { $node->tokenizer->reset(); - $writer = Latte\PhpWriter::using($node, $this->compiler); - if (is_string($def)) { - return $writer->write($def); - } else { - return call_user_func($def, $node, $writer); - } + $writer = Latte\PhpWriter::using($node); + return is_string($def) + ? $writer->write($def) + : call_user_func($def, $node, $writer); } @@ -153,4 +154,14 @@ return $this->compiler; } + + /** @internal */ + protected function checkExtraArgs(MacroNode $node) + { + if ($node->tokenizer->isNext()) { + $args = Latte\Runtime\Filters::truncate($node->tokenizer->joinAll(), 20); + trigger_error("Unexpected arguments '$args' in " . $node->getNotation()); + } + } + } diff -Nru php-nette-2.3.10/Nette/Latte/MacroTokens.php php-nette-2.4-20160731/Nette/Latte/MacroTokens.php --- php-nette-2.3.10/Nette/Latte/MacroTokens.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/MacroTokens.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,138 +0,0 @@ -parse($input)); - $this->ignored = array(self::T_COMMENT, self::T_WHITESPACE); - } - - - public function parse($s) - { - self::$tokenizer = self::$tokenizer ?: new Tokenizer(array( - self::T_WHITESPACE => '\s+', - self::T_COMMENT => '(?s)/\*.*?\*/', - self::T_STRING => Parser::RE_STRING, - self::T_KEYWORD => '(?:true|false|null|and|or|xor|clone|new|instanceof|return|continue|break|[A-Z_][A-Z0-9_]{2,})(?![\w\pL_])', // keyword or const - self::T_CAST => '\((?:expand|string|array|int|integer|float|bool|boolean|object)\)', // type casting - self::T_VARIABLE => '\$[\w\pL_]+', - self::T_NUMBER => '[+-]?[0-9]+(?:\.[0-9]+)?(?:e[0-9]+)?', - self::T_SYMBOL => '[\w\pL_]+(?:-[\w\pL_]+)*', - self::T_CHAR => '::|=>|->|\+\+|--|<<|>>|<=|>=|===|!==|==|!=|<>|&&|\|\||\?\?|[^"\']', // =>, any char except quotes - ), 'u'); - return self::$tokenizer->tokenize($s); - } - - - /** - * Appends simple token or string (will be parsed). - * @return self - */ - public function append($val, $position = NULL) - { - if ($val != NULL) { // intentionally @ - array_splice( - $this->tokens, - $position === NULL ? count($this->tokens) : $position, 0, - is_array($val) ? array($val) : $this->parse($val) - ); - } - return $this; - } - - - /** - * Prepends simple token or string (will be parsed). - * @return self - */ - public function prepend($val) - { - if ($val != NULL) { // intentionally @ - array_splice($this->tokens, 0, 0, is_array($val) ? array($val) : $this->parse($val)); - } - return $this; - } - - - /** - * Reads single token (optionally delimited by comma) from string. - * @param string - * @return string - */ - public function fetchWord() - { - $words = $this->fetchWords(); - return $words ? implode(':', $words) : FALSE; - } - - - /** - * Reads single tokens delimited by colon from string. - * @param string - * @return array - */ - public function fetchWords() - { - do { - $words[] = $this->joinUntil(self::T_WHITESPACE, ',', ':'); - } while ($this->nextToken(':')); - - if (count($words) === 1 && ($space = $this->nextValue(self::T_WHITESPACE)) - && (($dot = $this->nextValue('.')) || $this->isPrev('.'))) - { - $words[0] .= $space . $dot . $this->joinUntil(','); - } - $this->nextToken(','); - $this->nextAll(self::T_WHITESPACE, self::T_COMMENT); - return $words === array('') ? array() : $words; - } - - - public function reset() - { - $this->depth = 0; - return parent::reset(); - } - - - protected function next() - { - parent::next(); - if ($this->isCurrent('[', '(', '{')) { - $this->depth++; - } elseif ($this->isCurrent(']', ')', '}')) { - $this->depth--; - } - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Object.php php-nette-2.4-20160731/Nette/Latte/Object.php --- php-nette-2.3.10/Nette/Latte/Object.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Object.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -getMethods(\ReflectionMethod::IS_PUBLIC), $name)) ? ", did you mean $t()?" : '.'; - throw new LogicException("Call to undefined method $class::$name()$hint"); - } - - - /** - * Call to undefined static method. - * @throws LogicException - */ - public static function __callStatic($name, $args) - { - $rc = new \ReflectionClass(get_called_class()); - $items = array_intersect($rc->getMethods(\ReflectionMethod::IS_PUBLIC), $rc->getMethods(\ReflectionMethod::IS_STATIC)); - $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.'; - throw new LogicException("Call to undefined static method {$rc->getName()}::$name()$hint"); - } - - - /** - * Access to undeclared property. - * @throws LogicException - */ - public function &__get($name) - { - $rc = new \ReflectionClass($this); - $items = array_diff($rc->getProperties(\ReflectionProperty::IS_PUBLIC), $rc->getProperties(\ReflectionProperty::IS_STATIC)); - $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.'; - throw new LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint"); - } - - - /** - * Access to undeclared property. - * @throws LogicException - */ - public function __set($name, $value) - { - $rc = new \ReflectionClass($this); - $items = array_diff($rc->getProperties(\ReflectionProperty::IS_PUBLIC), $rc->getProperties(\ReflectionProperty::IS_STATIC)); - $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.'; - throw new LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint"); - } - - - /** - * @return bool - */ - public function __isset($name) - { - return FALSE; - } - - - /** - * Access to undeclared property. - * @throws LogicException - */ - public function __unset($name) - { - $class = get_class($this); - throw new LogicException("Attempt to unset undeclared property $class::$$name."); - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Parser.php php-nette-2.4-20160731/Nette/Latte/Parser.php --- php-nette-2.3.10/Nette/Latte/Parser.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Parser.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,463 +0,0 @@ - array('\\{(?![\\s\'"{}])', '\\}'), // {...} - 'double' => array('\\{\\{(?![\\s\'"{}])', '\\}\\}'), // {{...}} - 'asp' => array('<%\s*', '\s*%>'), /* <%...%> */ - 'python' => array('\\{[{%]\s*', '\s*[%}]\\}'), // {% ... %} | {{ ... }} - 'off' => array('[^\x00-\xFF]', ''), - ); - - /** @var string[] */ - private $delimiters; - - /** @var string source template */ - private $input; - - /** @var Token[] */ - private $output; - - /** @var int position on source template */ - private $offset; - - /** @var array */ - private $context; - - /** @var string */ - private $lastHtmlTag; - - /** @var string used by filter() */ - private $syntaxEndTag; - - /** @var int */ - private $syntaxEndLevel = 0; - - /** @var bool */ - private $xmlMode; - - /** @internal states */ - const CONTEXT_HTML_TEXT = 'htmlText', - CONTEXT_CDATA = 'cdata', - CONTEXT_HTML_TAG = 'htmlTag', - CONTEXT_HTML_ATTRIBUTE = 'htmlAttribute', - CONTEXT_RAW = 'raw', - CONTEXT_HTML_COMMENT = 'htmlComment', - CONTEXT_MACRO = 'macro'; - - - /** - * Process all {macros} and . - * @param string - * @return Token[] - */ - public function parse($input) - { - if (substr($input, 0, 3) === "\xEF\xBB\xBF") { // BOM - $input = substr($input, 3); - } - - $this->input = $input = str_replace("\r\n", "\n", $input); - $this->offset = 0; - $this->output = array(); - - if (!preg_match('##u', $input)) { - preg_match('#(?:[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})*+#A', $input, $m); - $this->offset = strlen($m[0]) + 1; - throw new \InvalidArgumentException('Template is not valid UTF-8 stream.'); - } - - $this->setSyntax($this->defaultSyntax); - $this->setContext(self::CONTEXT_HTML_TEXT); - $this->lastHtmlTag = $this->syntaxEndTag = NULL; - - $tokenCount = 0; - while ($this->offset < strlen($input)) { - if ($this->{'context' . $this->context[0]}() === FALSE) { - break; - } - while ($tokenCount < count($this->output)) { - $this->filter($this->output[$tokenCount++]); - } - } - if ($this->context[0] === self::CONTEXT_MACRO) { - throw new CompileException('Malformed macro'); - } - - if ($this->offset < strlen($input)) { - $this->addToken(Token::TEXT, substr($this->input, $this->offset)); - } - return $this->output; - } - - - /** - * Handles CONTEXT_HTML_TEXT. - */ - private function contextHtmlText() - { - $matches = $this->match('~ - (?:(?<=\n|^)[ \t]*)?<(?P /?)(?P [a-z0-9:]+)| ## begin of HTML tag !--(?!>))| ## begin of HTML comment - (?P ' . $this->delimiters[0] . ') - ~xsi'); - - if (!empty($matches['htmlcomment'])) { // )| ## end of HTML comment - (?P ' . $this->delimiters[0] . ') - ~xsi'); - - if (!empty($matches['htmlcomment'])) { // --> - $this->addToken(Token::HTML_TAG_END, $matches[0]); - $this->setContext(self::CONTEXT_HTML_TEXT); - } else { - return $this->processMacro($matches); - } - } - - - /** - * Handles CONTEXT_RAW. - */ - private function contextRaw() - { - $matches = $this->match('~ - (?P ' . $this->delimiters[0] . ') - ~xsi'); - return $this->processMacro($matches); - } - - - /** - * Handles CONTEXT_MACRO. - */ - private function contextMacro() - { - $matches = $this->match('~ - (?P \\*.*?\\*' . $this->delimiters[1] . '\n{0,2})| - (?P (?> - ' . self::RE_STRING . '| - \{(?>' . self::RE_STRING . '|[^\'"{}])*+\}| - [^\'"{}] - )+?) - ' . $this->delimiters[1] . ' - (?P [ \t]*(?=\n))? - ~xsiA'); - - if (!empty($matches['macro'])) { - $token = $this->addToken(Token::MACRO_TAG, $this->context[1][1] . $matches[0]); - list($token->name, $token->value, $token->modifiers, $token->empty) = $this->parseMacroTag($matches['macro']); - $this->context = $this->context[1][0]; - - } elseif (!empty($matches['comment'])) { - $this->addToken(Token::COMMENT, $this->context[1][1] . $matches[0]); - $this->context = $this->context[1][0]; - - } else { - throw new CompileException('Malformed macro'); - } - } - - - private function processMacro($matches) - { - if (!empty($matches['macro'])) { // {macro} or {* *} - $this->setContext(self::CONTEXT_MACRO, array($this->context, $matches['macro'])); - } else { - return FALSE; - } - } - - - /** - * Matches next token. - * @param string - * @return array - */ - private function match($re) - { - if (!preg_match($re, $this->input, $matches, PREG_OFFSET_CAPTURE, $this->offset)) { - if (preg_last_error()) { - throw new RegexpException(NULL, preg_last_error()); - } - return array(); - } - - $value = substr($this->input, $this->offset, $matches[0][1] - $this->offset); - if ($value !== '') { - $this->addToken(Token::TEXT, $value); - } - $this->offset = $matches[0][1] + strlen($matches[0][0]); - foreach ($matches as $k => $v) { - $matches[$k] = $v[0]; - } - return $matches; - } - - - /** - * @return self - */ - public function setContentType($type) - { - if (strpos($type, 'html') !== FALSE) { - $this->xmlMode = FALSE; - $this->setContext(self::CONTEXT_HTML_TEXT); - } elseif (strpos($type, 'xml') !== FALSE) { - $this->xmlMode = TRUE; - $this->setContext(self::CONTEXT_HTML_TEXT); - } else { - $this->setContext(self::CONTEXT_RAW); - } - return $this; - } - - - /** - * @return self - */ - public function setContext($context, $quote = NULL) - { - $this->context = array($context, $quote); - return $this; - } - - - /** - * Changes macro tag delimiters. - * @param string - * @return self - */ - public function setSyntax($type) - { - $type = $type ?: $this->defaultSyntax; - if (isset($this->syntaxes[$type])) { - $this->setDelimiters($this->syntaxes[$type][0], $this->syntaxes[$type][1]); - } else { - throw new \InvalidArgumentException("Unknown syntax '$type'"); - } - return $this; - } - - - /** - * Changes macro tag delimiters. - * @param string left regular expression - * @param string right regular expression - * @return self - */ - public function setDelimiters($left, $right) - { - $this->delimiters = array($left, $right); - return $this; - } - - - /** - * Parses macro tag to name, arguments a modifiers parts. - * @param string {name arguments | modifiers} - * @return array - * @internal - */ - public function parseMacroTag($tag) - { - if (!preg_match('~^ - ( - (?P \?|/?[a-z]\w*+(?:[.:]\w+)*+(?!::|\(|\\\\))| ## ?, name, /name, but not function( or class:: or namespace\ - (?P !?)(?P /?[=\~#%^&_]?) ## !expression, !=expression, ... - )(?P (?:' . self::RE_STRING . '|[^\'"])*?) - (?P (?/?\z) - ()\z~isx', $tag, $match)) { - if (preg_last_error()) { - throw new RegexpException(NULL, preg_last_error()); - } - return FALSE; - } - if ($match['name'] === '') { - $match['name'] = $match['shortname'] ?: '='; - if ($match['noescape']) { - if (!$this->shortNoEscape) { - trigger_error("The noescape shortcut {!...} is deprecated, use {...|noescape} modifier on line {$this->getLine()}.", E_USER_DEPRECATED); - } - $match['modifiers'] .= '|noescape'; - } - } - return array($match['name'], trim($match['args']), $match['modifiers'], (bool) $match['empty']); - } - - - private function addToken($type, $text) - { - $this->output[] = $token = new Token; - $token->type = $type; - $token->text = $text; - $token->line = $this->getLine(); - return $token; - } - - - public function getLine() - { - return $this->offset - ? substr_count(substr($this->input, 0, $this->offset - 1), "\n") + 1 - : 1; - } - - - /** - * Process low-level macros. - */ - protected function filter(Token $token) - { - if ($token->type === Token::MACRO_TAG && $token->name === '/syntax') { - $this->setSyntax($this->defaultSyntax); - $token->type = Token::COMMENT; - - } elseif ($token->type === Token::MACRO_TAG && $token->name === 'syntax') { - $this->setSyntax($token->value); - $token->type = Token::COMMENT; - - } elseif ($token->type === Token::HTML_ATTRIBUTE && $token->name === 'n:syntax') { - $this->setSyntax($token->value); - $this->syntaxEndTag = $this->lastHtmlTag; - $this->syntaxEndLevel = 1; - $token->type = Token::COMMENT; - - } elseif ($token->type === Token::HTML_TAG_BEGIN && $this->lastHtmlTag === $this->syntaxEndTag) { - $this->syntaxEndLevel++; - - } elseif ($token->type === Token::HTML_TAG_END && $this->lastHtmlTag === ('/' . $this->syntaxEndTag) && --$this->syntaxEndLevel === 0) { - $this->setSyntax($this->defaultSyntax); - - } elseif ($token->type === Token::MACRO_TAG && $token->name === 'contentType') { - $this->setContentType($token->value); - } - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/PhpWriter.php php-nette-2.4-20160731/Nette/Latte/PhpWriter.php --- php-nette-2.3.10/Nette/Latte/PhpWriter.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/PhpWriter.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,393 +0,0 @@ -tokenizer, $node->modifiers, $compiler); - } - - - public function __construct(MacroTokens $tokens, $modifiers = NULL, Compiler $compiler = NULL) - { - $this->tokens = $tokens; - $this->modifiers = $modifiers; - $this->compiler = $compiler; - } - - - /** - * Expands %node.word, %node.array, %node.args, %escape(), %modify(), %var, %raw, %word in code. - * @param string - * @return string - */ - public function write($mask) - { - $mask = preg_replace('#%(node|\d+)\.#', '%$1_', $mask); - $me = $this; - $mask = preg_replace_callback('#%escape(\(([^()]*+|(?1))+\))#', function ($m) use ($me) { - return $me->escapeFilter(new MacroTokens(substr($m[1], 1, -1)))->joinAll(); - }, $mask); - $mask = preg_replace_callback('#%modify(\(([^()]*+|(?1))+\))#', function ($m) use ($me) { - return $me->formatModifiers(substr($m[1], 1, -1)); - }, $mask); - - $args = func_get_args(); - $pos = $this->tokens->position; - $word = strpos($mask, '%node_word') === FALSE ? NULL : $this->tokens->fetchWord(); - - $code = preg_replace_callback('#([,+]\s*)?%(node_|\d+_|)(word|var|raw|array|args)(\?)?(\s*\+\s*)?()#', - function ($m) use ($me, $word, & $args) { - list(, $l, $source, $format, $cond, $r) = $m; - - switch ($source) { - case 'node_': - $arg = $word; break; - case '': - $arg = next($args); break; - default: - $arg = $args[$source + 1]; break; - } - - switch ($format) { - case 'word': - $code = $me->formatWord($arg); break; - case 'args': - $code = $me->formatArgs(); break; - case 'array': - $code = $me->formatArray(); - $code = $cond && $code === 'array()' ? '' : $code; break; - case 'var': - $code = var_export($arg, TRUE); break; - case 'raw': - $code = (string) $arg; break; - } - - if ($cond && $code === '') { - return $r ? $l : $r; - } else { - return $l . $code . $r; - } - }, $mask); - - $this->tokens->position = $pos; - return $code; - } - - - /** - * Formats modifiers calling. - * @param string - * @return string - */ - public function formatModifiers($var) - { - $tokens = new MacroTokens(ltrim($this->modifiers, '|')); - $tokens = $this->preprocess($tokens); - $tokens = $this->modifiersFilter($tokens, $var); - $tokens = $this->quoteFilter($tokens); - return $tokens->joinAll(); - } - - - /** - * Formats macro arguments to PHP code. (It advances tokenizer to the end as a side effect.) - * @return string - */ - public function formatArgs(MacroTokens $tokens = NULL) - { - $tokens = $this->preprocess($tokens); - $tokens = $this->quoteFilter($tokens); - return $tokens->joinAll(); - } - - - /** - * Formats macro arguments to PHP array. (It advances tokenizer to the end as a side effect.) - * @return string - */ - public function formatArray(MacroTokens $tokens = NULL) - { - $tokens = $this->preprocess($tokens); - $tokens = $this->expandFilter($tokens); - $tokens = $this->quoteFilter($tokens); - return $tokens->joinAll(); - } - - - /** - * Formats parameter to PHP string. - * @param string - * @return string - */ - public function formatWord($s) - { - return (is_numeric($s) || preg_match('#^\$|[\'"]|^true\z|^false\z|^null\z#i', $s)) - ? $this->formatArgs(new MacroTokens($s)) - : '"' . $s . '"'; - } - - - /** - * Preprocessor for tokens. (It advances tokenizer to the end as a side effect.) - * @return MacroTokens - */ - public function preprocess(MacroTokens $tokens = NULL) - { - $tokens = $tokens === NULL ? $this->tokens : $tokens; - $tokens = $this->removeCommentsFilter($tokens); - $tokens = $this->shortTernaryFilter($tokens); - $tokens = $this->shortArraysFilter($tokens); - return $tokens; - } - - - /** - * Removes PHP comments. - * @return MacroTokens - */ - public function removeCommentsFilter(MacroTokens $tokens) - { - $res = new MacroTokens; - while ($tokens->nextToken()) { - if (!$tokens->isCurrent(MacroTokens::T_COMMENT)) { - $res->append($tokens->currentToken()); - } - } - return $res; - } - - - /** - * Simplified ternary expressions without third part. - * @return MacroTokens - */ - public function shortTernaryFilter(MacroTokens $tokens) - { - $res = new MacroTokens; - $inTernary = array(); - while ($tokens->nextToken()) { - if ($tokens->isCurrent('?')) { - $inTernary[] = $tokens->depth; - - } elseif ($tokens->isCurrent(':')) { - array_pop($inTernary); - - } elseif ($tokens->isCurrent(',', ')', ']') && end($inTernary) === $tokens->depth + !$tokens->isCurrent(',')) { - $res->append(' : NULL'); - array_pop($inTernary); - } - $res->append($tokens->currentToken()); - } - - if ($inTernary) { - $res->append(' : NULL'); - } - return $res; - } - - - /** - * Simplified array syntax [...] - * @return MacroTokens - */ - public function shortArraysFilter(MacroTokens $tokens) - { - $res = new MacroTokens; - $arrays = array(); - while ($tokens->nextToken()) { - if ($tokens->isCurrent('[')) { - if ($arrays[] = !$tokens->isPrev(']', ')', MacroTokens::T_SYMBOL, MacroTokens::T_VARIABLE, MacroTokens::T_KEYWORD)) { - $res->append('array('); - continue; - - } - } elseif ($tokens->isCurrent(']')) { - if (array_pop($arrays) === TRUE) { - $res->append(')'); - continue; - } - } - $res->append($tokens->currentToken()); - } - return $res; - } - - - /** - * Pseudocast (expand). - * @return MacroTokens - */ - public function expandFilter(MacroTokens $tokens) - { - $res = new MacroTokens('array('); - $expand = NULL; - while ($tokens->nextToken()) { - if ($tokens->isCurrent('(expand)') && $tokens->depth === 0) { - $expand = TRUE; - $res->append('),'); - } elseif ($expand && $tokens->isCurrent(',') && !$tokens->depth) { - $expand = FALSE; - $res->append(', array('); - } else { - $res->append($tokens->currentToken()); - } - } - - if ($expand !== NULL) { - $res->prepend('array_merge(')->append($expand ? ', array()' : ')'); - } - return $res->append(')'); - } - - - /** - * Quotes symbols to strings. - * @return MacroTokens - */ - public function quoteFilter(MacroTokens $tokens) - { - $res = new MacroTokens; - while ($tokens->nextToken()) { - $res->append($tokens->isCurrent(MacroTokens::T_SYMBOL) - && (!$tokens->isPrev() || $tokens->isPrev(',', '(', '[', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', '=', 'and', 'or', 'xor', '??')) - && (!$tokens->isNext() || $tokens->isNext(',', ';', ')', ']', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', 'and', 'or', 'xor', '??')) - ? "'" . $tokens->currentValue() . "'" - : $tokens->currentToken() - ); - } - return $res; - } - - - /** - * Formats modifiers calling. - * @param MacroTokens - * @param string - * @throws CompileException - * @return MacroTokens - */ - public function modifiersFilter(MacroTokens $tokens, $var) - { - $inside = FALSE; - $res = new MacroTokens($var); - while ($tokens->nextToken()) { - if ($tokens->isCurrent(MacroTokens::T_WHITESPACE)) { - $res->append(' '); - - } elseif ($inside) { - if ($tokens->isCurrent(':', ',')) { - $res->append(', '); - $tokens->nextAll(MacroTokens::T_WHITESPACE); - - } elseif ($tokens->isCurrent('|')) { - $res->append(')'); - $inside = FALSE; - - } else { - $res->append($tokens->currentToken()); - } - } else { - if ($tokens->isCurrent(MacroTokens::T_SYMBOL)) { - if ($this->compiler && $tokens->isCurrent('escape')) { - $res = $this->escapeFilter($res); - $tokens->nextToken('|'); - } elseif (!strcasecmp($tokens->currentValue(), 'safeurl')) { - $res->prepend('Latte\Runtime\Filters::safeUrl('); - $inside = TRUE; - } else { - $res->prepend('$template->' . $tokens->currentValue() . '('); - $inside = TRUE; - } - } else { - throw new CompileException("Modifier name must be alphanumeric string, '{$tokens->currentValue()}' given."); - } - } - } - if ($inside) { - $res->append(')'); - } - return $res; - } - - - /** - * Escapes expression in tokens. - * @return MacroTokens - */ - public function escapeFilter(MacroTokens $tokens) - { - $tokens = clone $tokens; - switch ($this->compiler->getContentType()) { - case Compiler::CONTENT_XHTML: - case Compiler::CONTENT_HTML: - $context = $this->compiler->getContext(); - switch ($context[0]) { - case Compiler::CONTEXT_SINGLE_QUOTED_ATTR: - case Compiler::CONTEXT_DOUBLE_QUOTED_ATTR: - case Compiler::CONTEXT_UNQUOTED_ATTR: - if ($context[1] === Compiler::CONTENT_JS) { - $tokens->prepend('Latte\Runtime\Filters::escapeJs(')->append(')'); - } elseif ($context[1] === Compiler::CONTENT_CSS) { - $tokens->prepend('Latte\Runtime\Filters::escapeCss(')->append(')'); - } - $tokens->prepend('Latte\Runtime\Filters::escapeHtml(')->append($context[0] === Compiler::CONTEXT_SINGLE_QUOTED_ATTR ? ', ENT_QUOTES)' : ', ENT_COMPAT)'); - if ($context[0] === Compiler::CONTEXT_UNQUOTED_ATTR) { - $tokens->prepend("'\"', ")->append(", '\"'"); - } - return $tokens; - case Compiler::CONTEXT_COMMENT: - return $tokens->prepend('Latte\Runtime\Filters::escapeHtmlComment(')->append(')'); - case Compiler::CONTENT_JS: - case Compiler::CONTENT_CSS: - return $tokens->prepend('Latte\Runtime\Filters::escape' . ucfirst($context[0]) . '(')->append(')'); - default: - return $tokens->prepend('Latte\Runtime\Filters::escapeHtml(')->append(', ENT_NOQUOTES)'); - } - - case Compiler::CONTENT_XML: - $context = $this->compiler->getContext(); - switch ($context[0]) { - case Compiler::CONTEXT_COMMENT: - return $tokens->prepend('Latte\Runtime\Filters::escapeHtmlComment(')->append(')'); - default: - $tokens->prepend('Latte\Runtime\Filters::escapeXml(')->append(')'); - if ($context[0] === Compiler::CONTEXT_UNQUOTED_ATTR) { - $tokens->prepend("'\"', ")->append(", '\"'"); - } - return $tokens; - } - - case Compiler::CONTENT_JS: - case Compiler::CONTENT_CSS: - case Compiler::CONTENT_ICAL: - return $tokens->prepend('Latte\Runtime\Filters::escape' . ucfirst($this->compiler->getContentType()) . '(')->append(')'); - case Compiler::CONTENT_TEXT: - return $tokens; - default: - return $tokens->prepend('$template->escape(')->append(')'); - } - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/CachingIterator.php php-nette-2.4-20160731/Nette/Latte/Runtime/CachingIterator.php --- php-nette-2.3.10/Nette/Latte/Runtime/CachingIterator.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/CachingIterator.php 2016-07-31 17:46:38.000000000 +0000 @@ -25,6 +25,8 @@ */ class CachingIterator extends \CachingIterator implements \Countable { + use Latte\Strict; + /** @var int */ private $counter = 0; @@ -173,17 +175,7 @@ } - /********************* Latte\Object behaviour + property accessor ****************d*g**/ - - - /** - * Call to undefined method. - * @throws \LogicException - */ - public function __call($name, $args) - { - throw new \LogicException(sprintf('Call to undefined method %s::%s().', get_class($this), $name)); - } + /********************* property accessor ****************d*g**/ /** @@ -196,17 +188,7 @@ $ret = $this->$m(); return $ret; } - throw new \LogicException(sprintf('Attempt to read undeclared property %s::$%s.', get_class($this), $name)); - } - - - /** - * Access to undeclared property. - * @throws \LogicException - */ - public function __set($name, $value) - { - throw new \LogicException(sprintf('Attempt to write to undeclared property %s::$%s.', get_class($this), $name)); + throw new \LogicException('Attempt to read undeclared property ' . get_class($this) . "::\$$name."); } @@ -219,14 +201,4 @@ return method_exists($this, 'get' . $name) || method_exists($this, 'is' . $name); } - - /** - * Access to undeclared property. - * @throws \LogicException - */ - public function __unset($name) - { - throw new \LogicException(sprintf('Attempt to unset undeclared property %s::$%s.', get_class($this), $name)); - } - } diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/FilterExecutor.php php-nette-2.4-20160731/Nette/Latte/Runtime/FilterExecutor.php --- php-nette-2.3.10/Nette/Latte/Runtime/FilterExecutor.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/FilterExecutor.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,190 @@ + [callback, FilterInfo aware] */ + private $_static = [ + 'breaklines' => ['Latte\Runtime\Filters::breaklines', FALSE], + 'bytes' => ['Latte\Runtime\Filters::bytes', FALSE], + 'capitalize' => ['Latte\Runtime\Filters::capitalize', FALSE], + 'datastream' => ['Latte\Runtime\Filters::dataStream', FALSE], + 'date' => ['Latte\Runtime\Filters::date', FALSE], + 'escapecss' => ['Latte\Runtime\Filters::escapeCss', FALSE], + 'escapehtml' => ['Latte\Runtime\Filters::escapeHtml', FALSE], + 'escapehtmlcomment' => ['Latte\Runtime\Filters::escapeHtmlComment', FALSE], + 'escapeical' => ['Latte\Runtime\Filters::escapeICal', FALSE], + 'escapejs' => ['Latte\Runtime\Filters::escapeJs', FALSE], + 'escapeurl' => ['rawurlencode', FALSE], + 'escapexml' => ['Latte\Runtime\Filters::escapeXml', FALSE], + 'firstupper' => ['Latte\Runtime\Filters::firstUpper', FALSE], + 'checkurl' => ['Latte\Runtime\Filters::safeUrl', FALSE], + 'implode' => ['implode', FALSE], + 'indent' => ['Latte\Runtime\Filters::indent', TRUE], + 'length' => ['Latte\Runtime\Filters::length', FALSE], + 'lower' => ['Latte\Runtime\Filters::lower', FALSE], + 'nl2br' => ['Latte\Runtime\Filters::nl2br', FALSE], + 'number' => ['number_format', FALSE], + 'repeat' => ['Latte\Runtime\Filters::repeat', TRUE], + 'replace' => ['Latte\Runtime\Filters::replace', TRUE], + 'replacere' => ['Latte\Runtime\Filters::replaceRe', FALSE], + 'safeurl' => ['Latte\Runtime\Filters::safeUrl', FALSE], + 'strip' => ['Latte\Runtime\Filters::strip', TRUE], + 'striphtml' => ['Latte\Runtime\Filters::stripHtml', TRUE], + 'striptags' => ['Latte\Runtime\Filters::stripTags', TRUE], + 'substr' => ['Latte\Runtime\Filters::substring', FALSE], + 'trim' => ['Latte\Runtime\Filters::trim', FALSE], + 'truncate' => ['Latte\Runtime\Filters::truncate', FALSE], + 'upper' => ['Latte\Runtime\Filters::upper', FALSE], + ]; + + + /** + * Registers run-time filter. + * @param string|NULL + * @param callable + * @return self + */ + public function add($name, $callback) + { + if ($name == NULL) { // intentionally == + array_unshift($this->_dynamic, $callback); + } else { + $name = strtolower($name); + $this->_static[$name] = [$callback, NULL]; + unset($this->$name); + } + return $this; + } + + + /** + * Returns all run-time filters. + * @return string[] + */ + public function getAll() + { + return array_combine($tmp = array_keys($this->_static), $tmp); + } + + + /** + * Returns filter for classic calling. + * @return callable + */ + public function __get($name) + { + $lname = strtolower($name); + if (isset($this->$lname)) { // case mismatch + return $this->$lname; + + } elseif (isset($this->_static[$lname])) { + list($callback, $aware) = $this->prepareFilter($lname); + if ($aware) { // FilterInfo aware filter + return $this->$lname = function ($arg) use ($callback) { + $args = func_get_args(); + array_unshift($args, $info = new FilterInfo); + if ($arg instanceof IHtmlString) { + $args[1] = $arg->__toString(); + $info->contentType = Engine::CONTENT_HTML; + } + $res = call_user_func_array($callback, $args); + return $info->contentType === Engine::CONTENT_HTML + ? new Html($res) + : $res; + }; + } else { // classic filter + return $this->$lname = $callback; + } + } + + return $this->$lname = function ($arg) use ($lname, $name) { // dynamic filter + $args = func_get_args(); + array_unshift($args, $lname); + foreach ($this->_dynamic as $filter) { + $res = call_user_func_array(Helpers::checkCallback($filter), $args); + if ($res !== NULL) { + return $res; + } elseif (isset($this->_static[$lname])) { // dynamic converted to classic + $this->$name = Helpers::checkCallback($this->_static[$lname][0]); + return call_user_func_array($this->$name, func_get_args()); + } + } + $hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name)) ? ", did you mean '$t'?" : '.'; + throw new \LogicException("Filter '$name' is not defined$hint"); + }; + } + + + /** + * Calls filter with FilterInfo. + * @return mixed + */ + public function filterContent($name, FilterInfo $info, $arg) + { + $lname = strtolower($name); + $args = func_get_args(); + array_shift($args); + + if (!isset($this->_static[$lname])) { + $hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name)) ? ", did you mean '$t'?" : '.'; + throw new \LogicException("Filter |$name is not defined$hint"); + } + + list($callback, $aware) = $this->prepareFilter($lname); + if ($aware) { // FilterInfo aware filter + return call_user_func_array($callback, $args); + + } else { // classic filter + array_shift($args); + if ($info->contentType !== Engine::CONTENT_TEXT) { + trigger_error("Filter |$name is called with incompatible content type " . strtoupper($info->contentType) + . ($info->contentType === Engine::CONTENT_HTML ? ', try to prepend |stripHtml.' : '.'), E_USER_WARNING); + } + $res = call_user_func_array($this->$name, $args); + if ($res instanceof IHtmlString) { + trigger_error("Filter |$name should be changed to content-aware filter."); + $info->contentType = Engine::CONTENT_HTML; + $res = $res->__toString(); + } + return $res; + } + } + + + private function prepareFilter($name) + { + if (!isset($this->_static[$name][1])) { + $callback = Helpers::checkCallback($this->_static[$name][0]); + if (is_string($callback) && strpos($callback, '::')) { + $callback = explode('::', $callback); + } elseif (is_object($callback)) { + $callback = [$callback, '__invoke']; + } + $ref = is_array($callback) + ? new \ReflectionMethod($callback[0], $callback[1]) + : new \ReflectionFunction($callback); + $this->_static[$name][1] = ($tmp = $ref->getParameters()) + && $tmp[0]->getClass() && $tmp[0]->getClass()->getName() === 'Latte\Runtime\FilterInfo'; + } + return $this->_static[$name]; + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/FilterInfo.php php-nette-2.4-20160731/Nette/Latte/Runtime/FilterInfo.php --- php-nette-2.3.10/Nette/Latte/Runtime/FilterInfo.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/FilterInfo.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,29 @@ +contentType = $contentType; + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/Filters.php php-nette-2.4-20160731/Nette/Latte/Runtime/Filters.php --- php-nette-2.3.10/Nette/Latte/Runtime/Filters.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/Filters.php 2016-07-31 17:46:38.000000000 +0000 @@ -8,10 +8,11 @@ namespace Latte\Runtime; use Latte; +use Latte\Engine; /** - * Template filters. + * Template filters. Uses UTF-8 only. * @internal */ class Filters @@ -24,28 +25,73 @@ /** - * Escapes string for use inside HTML template. - * @param mixed UTF-8 encoding - * @param int optional attribute quotes - * @return string + * Escapes string for use inside HTML. + * @param mixed plain text + * @return string HTML */ - public static function escapeHtml($s, $quotes = ENT_QUOTES) + public static function escapeHtml($s) + { + return htmlSpecialChars($s, ENT_QUOTES, 'UTF-8'); + } + + + /** + * Escapes string for use inside HTML. + * @param mixed plain text or IHtmlString + * @return string HTML + */ + public static function escapeHtmlText($s) + { + return $s instanceof IHtmlString || $s instanceof \Nette\Utils\IHtmlString + ? $s->__toString(TRUE) + : htmlSpecialChars($s, ENT_NOQUOTES, 'UTF-8'); + } + + + /** + * Escapes string for use inside HTML attribute value. + * @param string plain text + * @return string HTML + */ + public static function escapeHtmlAttr($s, $double = TRUE) { - if ($s instanceof IHtmlString || $s instanceof \Nette\Utils\IHtmlString) { - return $s->__toString(TRUE); - } $s = (string) $s; - if ($quotes !== ENT_NOQUOTES && strpos($s, '`') !== FALSE && strpbrk($s, ' <>"\'') === FALSE) { - $s .= ' '; + if (strpos($s, '`') !== FALSE && strpbrk($s, ' <>"\'') === FALSE) { + $s .= ' '; // protection against innerHTML mXSS vulnerability nette/nette#1496 } - return htmlSpecialChars($s, $quotes, 'UTF-8'); + return htmlSpecialChars($s, ENT_QUOTES, 'UTF-8', $double); + } + + + /** + * Escapes HTML for use inside HTML attribute. + * @param mixed HTML text + * @return string HTML + */ + public static function escapeHtmlAttrConv($s) + { + return self::escapeHtmlAttr($s, FALSE); + } + + + /** + * Escapes string for use inside HTML attribute name. + * @param string plain text + * @return string HTML + */ + public static function escapeHtmlAttrUnquoted($s) + { + $s = (string) $s; + return preg_match('#^[a-z0-9:-]+$#i', $s) + ? $s + : '"' . self::escapeHtmlAttr($s) . '"'; } /** * Escapes string for use inside HTML comments. - * @param string UTF-8 encoding - * @return string + * @param string plain text + * @return string HTML */ public static function escapeHtmlComment($s) { @@ -63,10 +109,10 @@ /** * Escapes string for use inside XML 1.0 template. - * @param string UTF-8 encoding - * @return string + * @param string plain text + * @return string XML */ - public static function escapeXML($s) + public static function escapeXml($s) { // XML 1.0: \x09 \x0A \x0D and C1 allowed directly, C0 forbidden // XML 1.1: \x00 forbidden directly and as a character reference, @@ -76,9 +122,23 @@ /** + * Escapes string for use inside XML attribute name. + * @param string plain text + * @return string XML + */ + public static function escapeXmlAttrUnquoted($s) + { + $s = (string) $s; + return preg_match('#^[a-z0-9:-]+$#i', $s) + ? $s + : '"' . self::escapeXml($s) . '"'; + } + + + /** * Escapes string for use inside CSS template. - * @param string UTF-8 encoding - * @return string + * @param string plain text + * @return string CSS */ public static function escapeCss($s) { @@ -89,8 +149,8 @@ /** * Escapes variables for use inside = 1) { + if ($level < 1) { + // do nothing + } elseif (in_array($info->contentType, [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], TRUE)) { $s = preg_replace_callback('#<(textarea|pre).*?\\1#si', function ($m) { return strtr($m[0], " \t\r\n", "\x1F\x1E\x1D\x1A"); }, $s); @@ -165,16 +370,31 @@ } $s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s); $s = strtr($s, "\x1F\x1E\x1D\x1A", " \t\r\n"); + } else { + $s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s); } return $s; } /** + * Repeats text. + * @param + * @param string + * @param int + * @return string plain text + */ + public static function repeat(FilterInfo $info, $s, $count) + { + return str_repeat($s, $count); + } + + + /** * Date/time formatting. - * @param string|int|\DateTime|\DateInterval + * @param string|int|\DateTime|\DateTimeInterface|\DateInterval * @param string - * @return string + * @return string plain text */ public static function date($time, $format = NULL) { @@ -206,12 +426,12 @@ * Converts to human readable file size. * @param int * @param int - * @return string + * @return string plain text */ public static function bytes($bytes, $precision = 2) { $bytes = round($bytes); - $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB'); + $units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']; foreach ($units as $unit) { if (abs($bytes) < 1024 || $unit === end($units)) { break; @@ -224,12 +444,13 @@ /** * Performs a search and replace. + * @param * @param string * @param string * @param string * @return string */ - public static function replace($subject, $search, $replacement = '') + public static function replace(FilterInfo $info, $subject, $search, $replacement = '') { return str_replace($search, $replacement, $subject); } @@ -253,9 +474,9 @@ /** * The data: URI generator. + * @param string plain text * @param string - * @param string - * @return string + * @return string plain text */ public static function dataStream($data, $type = NULL) { @@ -272,12 +493,23 @@ */ public static function nl2br($value) { + trigger_error('Filter |nl2br is deprecated, use |breaklines which correctly handles escaping.', E_USER_DEPRECATED); return nl2br($value, self::$xhtml); } /** - * Returns a part of UTF-8 string. + * @param string plain text + * @return Html + */ + public static function breaklines($s) + { + return new Html(nl2br(htmlSpecialChars($s, ENT_NOQUOTES, 'UTF-8'), self::$xhtml)); + } + + + /** + * Returns a part of string. * @param string * @param int * @param int @@ -286,7 +518,7 @@ public static function substring($s, $start, $length = NULL) { if ($length === NULL) { - $length = self::length($s); + $length = strlen(utf8_decode($s)); } if (function_exists('mb_substr')) { return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster @@ -297,15 +529,15 @@ /** * Truncates string to maximal length. - * @param string UTF-8 encoding + * @param string plain text * @param int - * @param string UTF-8 encoding - * @return string + * @param string plain text + * @return string plain text */ public static function truncate($s, $maxLen, $append = "\xE2\x80\xA6") { - if (self::length($s) > $maxLen) { - $maxLen = $maxLen - self::length($append); + if (strlen(utf8_decode($s)) > $maxLen) { + $maxLen = $maxLen - strlen(utf8_decode($append)); if ($maxLen < 1) { return $append; @@ -322,7 +554,8 @@ /** * Convert to lower case. - * @return string + * @param string plain text + * @return string plain text */ public static function lower($s) { @@ -332,7 +565,8 @@ /** * Convert to upper case. - * @return string + * @param string plain text + * @return string plain text */ public static function upper($s) { @@ -342,7 +576,8 @@ /** * Convert first character to upper case. - * @return string + * @param string plain text + * @return string plain text */ public static function firstUpper($s) { @@ -352,7 +587,8 @@ /** * Capitalize string. - * @return string + * @param string plain text + * @return string plain text */ public static function capitalize($s) { @@ -361,20 +597,27 @@ /** - * Returns UTF-8 string length. + * Returns string length. + * @param array|\Countable|\Traversable|string * @return int */ - public static function length($s) + public static function length($val) { - return strlen(utf8_decode($s)); // fastest way + if (is_array($val) || $val instanceof \Countable) { + return count($val); + } elseif ($val instanceof \Traversable) { + return iterator_count($val); + } else { + return strlen(utf8_decode($val)); // fastest way + } } /** * Strips whitespace. - * @param string UTF-8 encoding - * @param string - * @return string + * @param string plain text + * @param string plain text + * @return string plain text */ public static function trim($s, $charlist = " \t\n\r\0\x0B\xC2\xA0") { @@ -431,8 +674,8 @@ $q = strpos($value, '"') === FALSE ? '"' : "'"; $s .= ' ' . $key . '=' . $q . str_replace( - array('&', $q, '<'), - array('&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'), + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], $value ) . (strpos($value, '`') !== FALSE && strpbrk($value, ' <>"\'') === FALSE ? ' ' : '') diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/Html.php php-nette-2.4-20160731/Nette/Latte/Runtime/Html.php --- php-nette-2.3.10/Nette/Latte/Runtime/Html.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/Html.php 2016-07-31 17:46:38.000000000 +0000 @@ -13,8 +13,10 @@ /** * HTML literal. */ -class Html extends Latte\Object implements IHtmlString +class Html implements IHtmlString { + use Latte\Strict; + /** @var string */ private $value; diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/ISnippetBridge.php php-nette-2.4-20160731/Nette/Latte/Runtime/ISnippetBridge.php --- php-nette-2.3.10/Nette/Latte/Runtime/ISnippetBridge.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/ISnippetBridge.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,65 @@ +bridge = $bridge; + } + + + public function enter($name, $type) + { + if (!$this->renderingSnippets) { + return; + } + $obStarted = FALSE; + if (($this->nestingLevel === 0 && $this->bridge->needsRedraw($name)) + || ($type === self::TYPE_DYNAMIC && ($previous = end($this->stack)) && $previous[1] === TRUE) + ) { + ob_start(function () {}); + $this->nestingLevel = $type === self::TYPE_AREA ? 0 : 1; + $obStarted = TRUE; + } elseif ($this->nestingLevel > 0) { + $this->nestingLevel++; + } + $this->stack[] = [$name, $obStarted]; + $this->bridge->markRedrawn($name); + } + + + public function leave() + { + if (!$this->renderingSnippets) { + return; + } + list($name, $obStarted) = array_pop($this->stack); + if ($this->nestingLevel > 0 && --$this->nestingLevel === 0) { + $content = ob_get_clean(); + $this->bridge->addSnippet($name, $content); + } elseif ($obStarted) { // dynamic snippet wrapper or snippet area + ob_end_clean(); + } + } + + + public function getHtmlId($name) + { + return $this->bridge->getHtmlId($name); + } + + + public function renderSnippets(array $blocks, array $params) + { + if ($this->renderingSnippets || !$this->bridge->isSnippetMode()) { + return FALSE; + } + $this->renderingSnippets = TRUE; + $this->bridge->setSnippetMode(FALSE); + foreach ($blocks as $name => $function) { + if ($name[0] !== '_' || !$this->bridge->needsRedraw(substr($name, 1))) { + continue; + } + $function = reset($function); + $function($params); + } + $this->bridge->setSnippetMode(TRUE); + $this->bridge->renderChildren(); + return TRUE; + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Runtime/Template.php php-nette-2.4-20160731/Nette/Latte/Runtime/Template.php --- php-nette-2.3.10/Nette/Latte/Runtime/Template.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Runtime/Template.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,390 @@ + method] @internal */ + protected $blocks = []; + + /** @var string|NULL|FALSE @internal */ + protected $parentName; + + /** @var Template|NULL @internal */ + private $referringTemplate; + + /** @var string|NULL @internal */ + private $referenceType; + + /** @var \stdClass global accumulators for intermediate results */ + public $global; + + /** @var [name => [callbacks]] @internal */ + protected $blockQueue = []; + + /** @var [name => type] @internal */ + protected $blockTypes = []; + + + public function __construct(Engine $engine, array $params, FilterExecutor $filters, array $providers, $name) + { + $this->engine = $engine; + $this->params = $params; + $this->filters = $filters; + $this->name = $name; + $this->global = (object) $providers; + foreach ($this->blocks as $nm => $method) { + $this->blockQueue[$nm][] = [$this, $method]; + } + $this->params['template'] = $this; // back compatibility + } + + + /** + * @return Engine + */ + public function getEngine() + { + return $this->engine; + } + + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + + /** + * Returns array of all parameters. + * @return array + */ + public function getParameters() + { + return $this->params; + } + + + /** + * Returns parameter. + * @return mixed + */ + public function getParameter($name) + { + if (!array_key_exists($name, $this->params)) { + trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE); + } + return $this->params[$name]; + } + + + /** + * @return string + */ + public function getContentType() + { + return $this->contentType; + } + + + /** + * @return string|NULL + */ + public function getParentName() + { + return $this->parentName ?: NULL; + } + + + /** + * @return Template|NULL + */ + public function getReferringTemplate() + { + return $this->referringTemplate; + } + + + /** + * @return string|NULL + */ + public function getReferenceType() + { + return $this->referenceType; + } + + + /** + * Renders template. + * @return void + * @internal + */ + public function render() + { + $this->prepare(); + + if ($this->parentName === NULL && isset($this->global->coreParentFinder)) { + $this->parentName = call_user_func($this->global->coreParentFinder, $this); + } + if (isset($this->global->snippetBridge) && !isset($this->global->snippetDriver)) { + $this->global->snippetDriver = new SnippetDriver($this->global->snippetBridge); + } + Filters::$xhtml = (bool) preg_match('#xml|xhtml#', $this->contentType); + + if ($this->referenceType === 'import') { + if ($this->parentName) { + $this->createTemplate($this->parentName, [], 'import')->render(); + } + return; + + } elseif ($this->parentName) { // extends + ob_start(function () {}); + $params = $this->main(); + ob_end_clean(); + $this->createTemplate($this->parentName, $params, 'extends')->render(); + return; + + } elseif (!empty($this->params['_renderblock'])) { // single block rendering + $tmp = $this; + while (in_array($this->referenceType, ['extends', NULL], TRUE) && ($tmp = $tmp->referringTemplate)); + if (!$tmp) { + $this->renderBlock($this->params['_renderblock'], $this->params); + return; + } + } + + // old accumulators for back compatibility + $this->params['_l'] = new \stdClass; + $this->params['_g'] = $this->global; + $this->params['_b'] = (object) ['blocks' => & $this->blockQueue, 'types' => & $this->blockTypes]; + if (isset($this->global->snippetDriver) && $this->global->snippetBridge->isSnippetMode()) { + if ($this->global->snippetDriver->renderSnippets($this->blockQueue, $this->params)) { + return; + } + } + + $this->main(); + } + + + /** + * Renders template. + * @return Template + * @internal + */ + protected function createTemplate($name, array $params, $referenceType) + { + $name = $this->engine->getLoader()->getReferredName($name, $this->name); + $child = $this->engine->createTemplate($name, $params); + $child->referringTemplate = $this; + $child->referenceType = $referenceType; + $child->global = $this->global; + if (in_array($referenceType, ['extends', 'includeblock', 'import'])) { + $this->blockQueue = array_merge_recursive($this->blockQueue, $child->blockQueue); + foreach ($child->blockTypes as $nm => $type) { + $this->checkBlockContentType($type, $nm); + } + $child->blockQueue = & $this->blockQueue; + $child->blockTypes = & $this->blockTypes; + } + return $child; + } + + + /** + * @param string|\Closure content-type name or modifier closure + * @return void + * @internal + */ + protected function renderToContentType($mod) + { + if ($mod && $mod !== $this->contentType) { + if ($filter = (is_string($mod) ? Filters::getConvertor($this->contentType, $mod) : $mod)) { + echo $filter($this->capture([$this, 'render']), $this->contentType); + return; + } + trigger_error("Including '$this->name' with content type " . strtoupper($this->contentType) . ' into incompatible type ' . strtoupper($mod) . '.', E_USER_WARNING); + } + $this->render(); + } + + + /** + * @return void + * @internal + */ + public function prepare() + { + } + + + /********************* blocks ****************d*g**/ + + + /** + * Renders block. + * @param string + * @param array + * @param string|\Closure content-type name or modifier closure + * @return void + * @internal + */ + protected function renderBlock($name, array $params, $mod = NULL) + { + if (empty($this->blockQueue[$name])) { + $hint = isset($this->blockQueue) && ($t = Latte\Helpers::getSuggestion(array_keys($this->blockQueue), $name)) ? ", did you mean '$t'?" : '.'; + throw new \RuntimeException("Cannot include undefined block '$name'$hint"); + } + + $block = reset($this->blockQueue[$name]); + if ($mod && $mod !== ($blockType = $this->blockTypes[$name])) { + if ($filter = (is_string($mod) ? Filters::getConvertor($blockType, $mod) : $mod)) { + echo $filter($this->capture(function () use ($block, $params) { $block($params); }), $blockType); + return; + } + trigger_error("Including block $name with content type " . strtoupper($blockType) . ' into incompatible type ' . strtoupper($mod) . '.', E_USER_WARNING); + } + $block($params); + } + + + /** + * Renders parent block. + * @return void + * @internal + */ + protected function renderBlockParent($name, array $params) + { + if (empty($this->blockQueue[$name]) || ($block = next($this->blockQueue[$name])) === FALSE) { + throw new \RuntimeException("Cannot include undefined parent block '$name'."); + } + $block($params); + prev($this->blockQueue[$name]); + } + + + /** + * @return void + * @internal + */ + protected function checkBlockContentType($current, $name) + { + $expected = & $this->blockTypes[$name]; + if ($expected === NULL) { + $expected = $current; + } elseif ($expected !== $current) { + trigger_error("Overridden block $name with content type " . strtoupper($current) . ' by incompatible type ' . strtoupper($expected) . '.', E_USER_WARNING); + } + } + + + /** + * Captures output to string. + * @return string + * @internal + */ + public function capture(callable $function) + { + ob_start(function () {}); + try { + $this->global->coreCaptured = TRUE; + $function(); + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + $this->global->coreCaptured = FALSE; + if (isset($e)) { + ob_end_clean(); + throw $e; + } + return ob_get_clean(); + } + + + /** @deprecated */ + public function setParameters(array $params) + { + trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED); + $this->params = $params; + return $this; + } + + + /********************* deprecated ****************d*g**/ + + + /** @deprecated */ + public function __call($name, $args) + { + trigger_error("Invoking filters via \$template->$name(\$vars) is deprecated, use (\$vars|$name)", E_USER_DEPRECATED); + return call_user_func_array($this->filters->$name, $args); + } + + + /** @deprecated */ + public function __set($name, $value) + { + trigger_error("Access to parameters via \$template->$name is deprecated", E_USER_DEPRECATED); + $this->params[$name] = $value; + } + + + /** @deprecated */ + public function &__get($name) + { + trigger_error("Access to parameters via \$template->$name is deprecated, use \$this->getParameter('$name')", E_USER_DEPRECATED); + if (!array_key_exists($name, $this->params)) { + trigger_error("The variable '$name' does not exist in template."); + } + return $this->params[$name]; + } + + + /** @deprecated */ + public function __isset($name) + { + trigger_error("Access to parameters via \$template->$name is deprecated, use isset(\$this->getParameters()['$name'])", E_USER_DEPRECATED); + return isset($this->params[$name]); + } + + + /** @deprecated */ + public function __unset($name) + { + trigger_error("Access to parameters via \$template->$name is deprecated.", E_USER_DEPRECATED); + unset($this->params[$name]); + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Strict.php php-nette-2.4-20160731/Nette/Latte/Strict.php --- php-nette-2.3.10/Nette/Latte/Strict.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Strict.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,90 @@ +getMethods(\ReflectionMethod::IS_PUBLIC); + $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.'; + throw new LogicException("Call to undefined method $class::$name()$hint"); + } + + + /** + * Call to undefined static method. + * @throws LogicException + */ + public static function __callStatic($name, $args) + { + $rc = new \ReflectionClass(get_called_class()); + $items = array_intersect($rc->getMethods(\ReflectionMethod::IS_PUBLIC), $rc->getMethods(\ReflectionMethod::IS_STATIC)); + $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.'; + throw new LogicException("Call to undefined static method {$rc->getName()}::$name()$hint"); + } + + + /** + * Access to undeclared property. + * @throws LogicException + */ + public function &__get($name) + { + $rc = new \ReflectionClass($this); + $items = array_diff($rc->getProperties(\ReflectionProperty::IS_PUBLIC), $rc->getProperties(\ReflectionProperty::IS_STATIC)); + $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.'; + throw new LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint"); + } + + + /** + * Access to undeclared property. + * @throws LogicException + */ + public function __set($name, $value) + { + $rc = new \ReflectionClass($this); + $items = array_diff($rc->getProperties(\ReflectionProperty::IS_PUBLIC), $rc->getProperties(\ReflectionProperty::IS_STATIC)); + $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.'; + throw new LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint"); + } + + + /** + * @return bool + */ + public function __isset($name) + { + return FALSE; + } + + + /** + * Access to undeclared property. + * @throws LogicException + */ + public function __unset($name) + { + $class = get_class($this); + throw new LogicException("Attempt to unset undeclared property $class::$$name."); + } + +} diff -Nru php-nette-2.3.10/Nette/Latte/Template.php php-nette-2.4-20160731/Nette/Latte/Template.php --- php-nette-2.3.10/Nette/Latte/Template.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Template.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,176 +0,0 @@ -setParameters($params); - $this->engine = $engine; - $this->name = $name; - } - - - /** - * @return Engine - */ - public function getEngine() - { - return $this->engine; - } - - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - - /** - * Initializes block, global & local storage in template. - * @return [\stdClass, \stdClass, \stdClass] - * @internal - */ - public function initialize($templateId, $contentType) - { - Runtime\Filters::$xhtml = (bool) preg_match('#xml|xhtml#', $contentType); - - // local storage - $this->params['_l'] = new \stdClass; - - // block storage - if (isset($this->params['_b'])) { - $block = $this->params['_b']; - unset($this->params['_b']); - } else { - $block = new \stdClass; - } - $block->templates[$templateId] = $this; - - // global storage - if (!isset($this->params['_g'])) { - $this->params['_g'] = new \stdClass; - } - - return array($block, $this->params['_g'], $this->params['_l']); - } - - - /** - * Renders template. - * @return void - * @internal - */ - public function renderChildTemplate($name, array $params = array()) - { - $name = $this->engine->getLoader()->getChildName($name, $this->name); - $this->engine->render($name, $params); - } - - - /** - * Call a template run-time filter. Do not call directly. - * @param string filter name - * @param array arguments - * @return mixed - */ - public function __call($name, $args) - { - return $this->engine->invokeFilter($name, $args); - } - - - /********************* template parameters ****************d*g**/ - - - /** - * Sets all parameters. - * @param array - * @return self - */ - public function setParameters(array $params) - { - $this->params = $params; - $this->params['template'] = $this; - return $this; - } - - - /** - * Returns array of all parameters. - * @return array - */ - public function getParameters() - { - return $this->params; - } - - - /** - * Sets a template parameter. Do not call directly. - * @return void - */ - public function __set($name, $value) - { - $this->params[$name] = $value; - } - - - /** - * Returns a template parameter. Do not call directly. - * @return mixed value - */ - public function &__get($name) - { - if (!array_key_exists($name, $this->params)) { - trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE); - } - return $this->params[$name]; - } - - - /** - * Determines whether parameter is defined. Do not call directly. - * @return bool - */ - public function __isset($name) - { - return isset($this->params[$name]); - } - - - /** - * Removes a template parameter. Do not call directly. - * @param string name - * @return void - */ - public function __unset($name) - { - unset($this->params[$name]); - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/TokenIterator.php php-nette-2.4-20160731/Nette/Latte/TokenIterator.php --- php-nette-2.3.10/Nette/Latte/TokenIterator.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/TokenIterator.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,228 +0,0 @@ -tokens = $tokens; - } - - - /** - * Returns current token. - * @return array|NULL - */ - public function currentToken() - { - return isset($this->tokens[$this->position]) - ? $this->tokens[$this->position] - : NULL; - } - - - /** - * Returns current token value. - * @return string|NULL - */ - public function currentValue() - { - return isset($this->tokens[$this->position]) - ? $this->tokens[$this->position][Tokenizer::VALUE] - : NULL; - } - - - /** - * Returns next token. - * @param int|string (optional) desired token type or value - * @return array|NULL - */ - public function nextToken() - { - return $this->scan(func_get_args(), TRUE, TRUE); // onlyFirst, advance - } - - - /** - * Returns next token value. - * @param int|string (optional) desired token type or value - * @return string|NULL - */ - public function nextValue() - { - return $this->scan(func_get_args(), TRUE, TRUE, TRUE); // onlyFirst, advance, strings - } - - - /** - * Returns all next tokens. - * @param int|string (optional) desired token type or value - * @return array[] - */ - public function nextAll() - { - return $this->scan(func_get_args(), FALSE, TRUE); // advance - } - - - /** - * Returns all next tokens until it sees a given token type or value. - * @param int|string token type or value to stop before - * @return array[] - */ - public function nextUntil($arg) - { - return $this->scan(func_get_args(), FALSE, TRUE, FALSE, TRUE); // advance, until - } - - - /** - * Returns concatenation of all next token values. - * @param int|string (optional) token type or value to be joined - * @return string - */ - public function joinAll() - { - return $this->scan(func_get_args(), FALSE, TRUE, TRUE); // advance, strings - } - - - /** - * Returns concatenation of all next tokens until it sees a given token type or value. - * @param int|string token type or value to stop before - * @return string - */ - public function joinUntil($arg) - { - return $this->scan(func_get_args(), FALSE, TRUE, TRUE, TRUE); // advance, strings, until - } - - - /** - * Checks the current token. - * @param int|string token type or value - * @return bool - */ - public function isCurrent($arg) - { - if (!isset($this->tokens[$this->position])) { - return FALSE; - } - $args = func_get_args(); - $token = $this->tokens[$this->position]; - return in_array($token[Tokenizer::VALUE], $args, TRUE) - || (isset($token[Tokenizer::TYPE]) && in_array($token[Tokenizer::TYPE], $args, TRUE)); - } - - - /** - * Checks the next token existence. - * @param int|string (optional) token type or value - * @return bool - */ - public function isNext() - { - return (bool) $this->scan(func_get_args(), TRUE, FALSE); // onlyFirst - } - - - /** - * Checks the previous token existence. - * @param int|string (optional) token type or value - * @return bool - */ - public function isPrev() - { - return (bool) $this->scan(func_get_args(), TRUE, FALSE, FALSE, FALSE, TRUE); // onlyFirst, prev - } - - - /** - * @return self - */ - public function reset() - { - $this->position = -1; - return $this; - } - - - /** - * Moves cursor to next token. - */ - protected function next() - { - $this->position++; - } - - - /** - * Looks for (first) (not) wanted tokens. - * @param array of desired token types or values - * @param bool - * @param bool - * @param bool - * @param bool - * @param bool - * @return mixed - */ - protected function scan($wanted, $onlyFirst, $advance, $strings = FALSE, $until = FALSE, $prev = FALSE) - { - $res = $onlyFirst ? NULL : ($strings ? '' : array()); - $pos = $this->position + ($prev ? -1 : 1); - do { - if (!isset($this->tokens[$pos])) { - if (!$wanted && $advance && !$prev && $pos <= count($this->tokens)) { - $this->next(); - } - return $res; - } - - $token = $this->tokens[$pos]; - $type = isset($token[Tokenizer::TYPE]) ? $token[Tokenizer::TYPE] : NULL; - if (!$wanted || (in_array($token[Tokenizer::VALUE], $wanted, TRUE) || in_array($type, $wanted, TRUE)) ^ $until) { - while ($advance && !$prev && $pos > $this->position) { - $this->next(); - } - - if ($onlyFirst) { - return $strings ? $token[Tokenizer::VALUE] : $token; - } elseif ($strings) { - $res .= $token[Tokenizer::VALUE]; - } else { - $res[] = $token; - } - - } elseif ($until || !in_array($type, $this->ignored, TRUE)) { - return $res; - } - $pos += $prev ? -1 : 1; - } while (TRUE); - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Tokenizer.php php-nette-2.4-20160731/Nette/Latte/Tokenizer.php --- php-nette-2.3.10/Nette/Latte/Tokenizer.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Tokenizer.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ - pattern] - * @param string regular expression flag - */ - public function __construct(array $patterns, $flags = '') - { - $this->re = '~(' . implode(')|(', $patterns) . ')~A' . $flags; - $this->types = array_keys($patterns); - } - - - /** - * Tokenizes string. - * @param string - * @return array - */ - public function tokenize($input) - { - preg_match_all($this->re, $input, $tokens, PREG_SET_ORDER); - if (preg_last_error()) { - throw new RegexpException(NULL, preg_last_error()); - } - $len = 0; - $count = count($this->types); - foreach ($tokens as & $match) { - $type = NULL; - for ($i = 1; $i <= $count; $i++) { - if (!isset($match[$i])) { - break; - } elseif ($match[$i] != NULL) { - $type = $this->types[$i - 1]; - break; - } - } - $match = array(self::VALUE => $match[0], self::OFFSET => $len, self::TYPE => $type); - $len += strlen($match[self::VALUE]); - } - if ($len !== strlen($input)) { - list($line, $col) = $this->getCoordinates($input, $len); - $token = str_replace("\n", '\n', substr($input, $len, 10)); - throw new CompileException("Unexpected '$token' on line $line, column $col."); - } - return $tokens; - } - - - /** - * Returns position of token in input string. - * @param string - * @param int - * @return array of [line, column] - */ - public static function getCoordinates($text, $offset) - { - $text = substr($text, 0, $offset); - return array(substr_count($text, "\n") + 1, $offset - strrpos("\n" . $text, "\n") + 1); - } - -} diff -Nru php-nette-2.3.10/Nette/Latte/Token.php php-nette-2.4-20160731/Nette/Latte/Token.php --- php-nette-2.3.10/Nette/Latte/Token.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Latte/Token.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -? used for type HTML_TAG_BEGIN */ - public $closing; - - /** @var bool is tag empty {name/}? used for type MACRO_TAG */ - public $empty; - -} diff -Nru php-nette-2.3.10/Nette/Loaders/NetteLoader.php php-nette-2.4-20160731/Nette/Loaders/NetteLoader.php --- php-nette-2.3.10/Nette/Loaders/NetteLoader.php 2016-04-13 18:50:56.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Loaders/NetteLoader.php 2016-07-31 17:46:58.000000000 +0000 @@ -66,33 +66,37 @@ /** @var array */ public $list = array( 'Latte\CompileException' => 'Latte/exceptions', - 'Latte\Compiler' => 'Latte/Compiler', + 'Latte\Compiler' => 'Latte/Compiler/Compiler', 'Latte\Engine' => 'Latte/Engine', 'Latte\Helpers' => 'Latte/Helpers', - 'Latte\HtmlNode' => 'Latte/HtmlNode', + 'Latte\HtmlNode' => 'Latte/Compiler/HtmlNode', 'Latte\ILoader' => 'Latte/ILoader', 'Latte\IMacro' => 'Latte/IMacro', 'Latte\Loaders\FileLoader' => 'Latte/Loaders/FileLoader', 'Latte\Loaders\StringLoader' => 'Latte/Loaders/StringLoader', - 'Latte\MacroNode' => 'Latte/MacroNode', - 'Latte\MacroTokens' => 'Latte/MacroTokens', + 'Latte\MacroNode' => 'Latte/Compiler/MacroNode', + 'Latte\MacroTokens' => 'Latte/Compiler/MacroTokens', 'Latte\Macros\BlockMacros' => 'Latte/Macros/BlockMacros', - 'Latte\Macros\BlockMacrosRuntime' => 'Latte/Macros/BlockMacrosRuntime', 'Latte\Macros\CoreMacros' => 'Latte/Macros/CoreMacros', 'Latte\Macros\MacroSet' => 'Latte/Macros/MacroSet', - 'Latte\Object' => 'Latte/Object', - 'Latte\Parser' => 'Latte/Parser', - 'Latte\PhpWriter' => 'Latte/PhpWriter', + 'Latte\Parser' => 'Latte/Compiler/Parser', + 'Latte\PhpHelpers' => 'Latte/Compiler/PhpHelpers', + 'Latte\PhpWriter' => 'Latte/Compiler/PhpWriter', 'Latte\RegexpException' => 'Latte/exceptions', 'Latte\RuntimeException' => 'Latte/exceptions', 'Latte\Runtime\CachingIterator' => 'Latte/Runtime/CachingIterator', + 'Latte\Runtime\FilterExecutor' => 'Latte/Runtime/FilterExecutor', + 'Latte\Runtime\FilterInfo' => 'Latte/Runtime/FilterInfo', 'Latte\Runtime\Filters' => 'Latte/Runtime/Filters', 'Latte\Runtime\Html' => 'Latte/Runtime/Html', 'Latte\Runtime\IHtmlString' => 'Latte/Runtime/IHtmlString', - 'Latte\Template' => 'Latte/Template', - 'Latte\Token' => 'Latte/Token', - 'Latte\TokenIterator' => 'Latte/TokenIterator', - 'Latte\Tokenizer' => 'Latte/Tokenizer', + 'Latte\Runtime\ISnippetBridge' => 'Latte/Runtime/ISnippetBridge', + 'Latte\Runtime\SnippetDriver' => 'Latte/Runtime/SnippetDriver', + 'Latte\Runtime\Template' => 'Latte/Runtime/Template', + 'Latte\Strict' => 'Latte/Strict', + 'Latte\Token' => 'Latte/Compiler/Token', + 'Latte\TokenIterator' => 'Latte/Compiler/TokenIterator', + 'Latte\Tokenizer' => 'Latte/Compiler/Tokenizer', 'NetteModule\ErrorPresenter' => 'Application/ErrorPresenter', 'NetteModule\MicroPresenter' => 'Application/MicroPresenter', 'Nette\Application\AbortException' => 'Application/exceptions', @@ -110,12 +114,7 @@ 'Nette\Database\ConstraintViolationException' => 'Database/exceptions', 'Nette\Database\Conventions\AmbiguousReferenceKeyException' => 'Database/Conventions/exceptions', 'Nette\Database\ForeignKeyConstraintViolationException' => 'Database/exceptions', - 'Nette\Database\IReflection' => 'Database/deprecated/IReflection', 'Nette\Database\NotNullConstraintViolationException' => 'Database/exceptions', - 'Nette\Database\Reflection\AmbiguousReferenceKeyException' => 'Database/deprecated/exceptions', - 'Nette\Database\Reflection\ConventionalReflection' => 'Database/deprecated/ConventionalReflection', - 'Nette\Database\Reflection\DiscoveredReflection' => 'Database/deprecated/DiscoveredReflection', - 'Nette\Database\Reflection\MissingReferenceException' => 'Database/deprecated/exceptions', 'Nette\Database\UniqueConstraintViolationException' => 'Database/exceptions', 'Nette\DeprecatedException' => 'Utils/exceptions', 'Nette\Diagnostics\Debugger' => 'deprecated/Diagnostics/Debugger', @@ -132,6 +131,7 @@ 'Nette\Latte\Engine' => 'deprecated/Latte/Engine', 'Nette\Loaders\RobotLoader' => 'RobotLoader/RobotLoader', 'Nette\Localization\ITranslator' => 'Utils/ITranslator', + 'Nette\Mail\FallbackMailerException' => 'Mail/exceptions', 'Nette\Mail\SendException' => 'Mail/exceptions', 'Nette\Mail\SmtpException' => 'Mail/exceptions', 'Nette\MemberAccessException' => 'Utils/exceptions', @@ -139,6 +139,8 @@ 'Nette\NotSupportedException' => 'Utils/exceptions', 'Nette\Object' => 'Utils/Object', 'Nette\OutOfRangeException' => 'Utils/exceptions', + 'Nette\SmartObject' => 'Utils/SmartObject', + 'Nette\StaticClass' => 'Utils/StaticClass', 'Nette\StaticClassException' => 'Utils/exceptions', 'Nette\Templating\FileTemplate' => 'deprecated/Templating/FileTemplate', 'Nette\Templating\Helpers' => 'deprecated/Templating/Helpers', @@ -147,13 +149,11 @@ 'Nette\Templating\Template' => 'deprecated/Templating/Template', 'Nette\UnexpectedValueException' => 'Utils/exceptions', 'Nette\Utils\AssertionException' => 'Utils/exceptions', - 'Nette\Utils\CallbackFilterIterator' => 'Finder/CallbackFilterIterator', 'Nette\Utils\Finder' => 'Finder/Finder', 'Nette\Utils\ImageException' => 'Utils/exceptions', 'Nette\Utils\JsonException' => 'Utils/exceptions', 'Nette\Utils\LimitedScope' => 'deprecated/Utils/LimitedScope', 'Nette\Utils\MimeTypeDetector' => 'deprecated/Utils/MimeTypeDetector', - 'Nette\Utils\RecursiveCallbackFilterIterator' => 'Finder/RecursiveCallbackFilterIterator', 'Nette\Utils\RegexpException' => 'Utils/exceptions', 'Nette\Utils\SafeStream' => 'SafeStream/SafeStream', 'Nette\Utils\TokenIterator' => 'Tokenizer/TokenIterator', diff -Nru php-nette-2.3.10/Nette/Mail/exceptions.php php-nette-2.4-20160731/Nette/Mail/exceptions.php --- php-nette-2.3.10/Nette/Mail/exceptions.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Mail/exceptions.php 2016-07-31 17:46:38.000000000 +0000 @@ -24,3 +24,10 @@ class SmtpException extends SendException { } + + +class FallbackMailerException extends SendException +{ + /** @var SendException[] */ + public $failures; +} diff -Nru php-nette-2.3.10/Nette/Mail/FallbackMailer.php php-nette-2.4-20160731/Nette/Mail/FallbackMailer.php --- php-nette-2.3.10/Nette/Mail/FallbackMailer.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Mail/FallbackMailer.php 2016-07-31 17:46:38.000000000 +0000 @@ -0,0 +1,86 @@ +mailers = $mailers; + $this->retryCount = $retryCount; + $this->retryWaitTime = $retryWaitTime; + } + + + /** + * Sends email. + * @return void + * @throws FallbackMailerException + */ + public function send(Message $mail) + { + if (!$this->mailers) { + throw new Nette\InvalidArgumentException('At least one mailer must be provided.'); + } + + for ($i = 0; $i < $this->retryCount; $i++) { + if ($i > 0) { + usleep($this->retryWaitTime * 1000); + } + + foreach ($this->mailers as $mailer) { + try { + $mailer->send($mail); + return; + + } catch (SendException $e) { + $failures[] = $e; + $this->onFailure($this, $e, $mailer, $mail); + } + } + } + + $e = new FallbackMailerException('All mailers failed to send the message.'); + $e->failures = $failures; + throw $e; + } + + + /** + * @return self + */ + public function addMailer(IMailer $mailer) + { + $this->mailers[] = $mailer; + return $this; + } + +} diff -Nru php-nette-2.3.10/Nette/Mail/Message.php php-nette-2.4-20160731/Nette/Mail/Message.php --- php-nette-2.3.10/Nette/Mail/Message.php 2016-04-13 18:50:44.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Mail/Message.php 2016-07-31 17:46:38.000000000 +0000 @@ -25,16 +25,16 @@ LOW = 5; /** @var array */ - public static $defaultHeaders = array( + public static $defaultHeaders = [ 'MIME-Version' => '1.0', 'X-Mailer' => 'Nette Framework', - ); + ]; /** @var array */ - private $attachments = array(); + private $attachments = []; /** @var array */ - private $inlines = array(); + private $inlines = []; /** @var mixed */ private $html; @@ -155,9 +155,9 @@ private function formatEmail($email, $name) { if (!$name && preg_match('#^(.+) +<(.*)>\z#', $email, $matches)) { - return array($matches[2] => $matches[1]); + return [$matches[2] => $matches[1]]; } else { - return array($email => $name); + return [$email => $name]; } } @@ -214,14 +214,10 @@ */ public function setHtmlBody($html, $basePath = NULL) { - if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) { - $basePath = dirname($html->getFile()); - $bc = TRUE; - } $html = (string) $html; if ($basePath) { - $cids = array(); + $cids = []; $matches = Strings::matchAll( $html, '# @@ -230,14 +226,12 @@ |<[^<>]+\s style\s*=\s* ["\'][^"\'>]+[:\s] url\( | +$tabs = function ($panels) { + foreach ($panels as $panel) { + if ($panel->tab) { ?> + panel): ?>= trim($panel->tab) ?>', trim($panel->tab), ' - -- +type === 'main'): ?> - --diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/dumps.panel.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/dumps.panel.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/dumps.panel.phtml 2016-04-13 18:50:56.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/dumps.panel.phtml 2016-07-31 17:47:00.000000000 +0000 @@ -19,9 +19,9 @@+ +type === 'main'): ?>- - - - +type === 'redirect'): ?> +-
- "> +
- ">
- -'; ?> -
-- ', trim($panel['tab']), ''; endif ?>
- + panels) ?>- ×
+
+type === 'ajax'): ?> +- redirect
+ panels) ?> ++
+ + - +type === 'main'): ?>- AJAX
+ panels) ?> +- +diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/errors.panel.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/errors.panel.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/errors.panel.phtml 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/errors.panel.phtml 2016-07-31 17:46:44.000000000 +0000 @@ -17,8 +17,8 @@= Helpers::escapeHtml($item['title']) ?>
- + = $item['dump'] ?>$count): list($file, $line, $message) = explode('|', $item, 3) ?>
diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/errors.tab.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/errors.tab.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/errors.tab.phtml 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/errors.tab.phtml 2016-07-31 17:46:44.000000000 +0000 @@ -24,5 +24,5 @@ padding: 1px .4em; "> 1 ? ' errors' : ' error' ?> += $sum = array_sum($data), $sum > 1 ? ' errors' : ' error' ?> diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/info.panel.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/info.panel.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/info.panel.phtml 2016-04-13 18:50:56.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/info.panel.phtml 2016-07-31 17:47:00.000000000 +0000 @@ -19,21 +19,22 @@ $systemUsage = -round(($this->cpuUsage['ru_stime.tv_sec'] * 1e6 + $this->cpuUsage['ru_stime.tv_usec']) / $this->time / 10000); } -$info = array_filter(array( - 'Execution time' => str_replace(' ', ' ', number_format($this->time * 1000, 1, '.', ' ')) . ' ms', +$info = array_filter([ + 'Execution time' => number_format($this->time * 1000, 1, '.', ' ') . ' ms', 'CPU usage user + system' => isset($userUsage) ? (int) $userUsage . ' % + ' . (int) $systemUsage . ' %' : NULL, - 'Peak of allocated memory' => str_replace(' ', ' ', number_format(memory_get_peak_usage() / 1000000, 2, '.', ' ')) . ' MB', + 'Peak of allocated memory' => number_format(memory_get_peak_usage() / 1000000, 2, '.', ' ') . ' MB', 'Included files' => count(get_included_files()), 'Classes + interfaces + traits' => count(get_declared_classes()) . ' + ' - . count(get_declared_interfaces()) . ' + ' . (PHP_VERSION_ID >= 50400 ? count(get_declared_traits()) : '0'), + . count(get_declared_interfaces()) . ' + ' . count(get_declared_traits()), 'Your IP' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL, 'Server IP' => isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : NULL, + 'HTTP method / response code' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] . ' / ' . http_response_code() : NULL, 'HHVM' => defined('HHVM_VERSION') ? HHVM_VERSION : NULL, 'PHP' => PHP_VERSION, 'Xdebug' => extension_loaded('xdebug') ? phpversion('xdebug') : NULL, 'Tracy' => Debugger::VERSION, 'Server' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL, -) + (array) $this->data); +] + (array) $this->data); ?> @@ -45,9 +46,9 @@ $val): ?>- - + = $count ? "$count\xC3\x97" : '' ?> + = Helpers::escapeHtml($message), ' in ', Helpers::editorLink($file, $line) ?>25): ?> - diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/info.tab.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/info.tab.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/info.tab.phtml 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/info.tab.phtml 2016-07-31 17:46:44.000000000 +0000 @@ -16,5 +16,5 @@ ?> time * 1000, 1, '.', ' ')) ?> ms += number_format($this->time * 1000, 1, '.', ' ') ?> ms diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/loader.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/loader.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/loader.phtml 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/loader.phtml 2016-07-31 17:46:44.000000000 +0000 @@ -0,0 +1,31 @@ + + + + + + + + 'css', 'v' => Debugger::VERSION] + $stopXdebug, NULL, '&') ?>" id="tracy-debug-style" class="tracy-debug" /> + + + + + + diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Bar/panels.phtml php-nette-2.4-20160731/Nette/Tracy/assets/Bar/panels.phtml --- php-nette-2.3.10/Nette/Tracy/assets/Bar/panels.phtml 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Bar/panels.phtml 2016-07-31 17:46:44.000000000 +0000 @@ -0,0 +1,30 @@ + + ¤ + × ++ = Helpers::escapeHtml($key) ?> = Helpers::escapeHtml($val) ?> -+ = Helpers::escapeHtml($key) ?> = Helpers::escapeHtml($val) ?> getCode() ? ' #' . $exception->getCode() : ''; - -?> getMessage() . $code, ENT_IGNORE, 'UTF-8') ?> - getPrevious()): ?> - - - - - - - - - -- --- - - - - diff -Nru php-nette-2.3.10/Nette/Tracy/assets/BlueScreen/content.phtml php-nette-2.4-20160731/Nette/Tracy/assets/BlueScreen/content.phtml --- php-nette-2.3.10/Nette/Tracy/assets/BlueScreen/content.phtml 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/BlueScreen/content.phtml 2016-07-31 17:46:44.000000000 +0000 @@ -0,0 +1,335 @@ +getCode() ? ' #' . $exception->getCode() : ''; + +?> +- getMessage()): ?> - - -- - getPrevious()): ?> -getMessage() ?: $title . $code, ENT_IGNORE, 'UTF-8') ?> - getMessage())) ?>" target="_blank" rel="noreferrer">search► - skip error►
-- Caused by -- - - - - - - -> --Caused by
- --' ?> - - - count((array) new \Exception)):?> --- - - - - - - - - - - getTrace(); $expanded = NULL ?> - getSeverity(), array(E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED))) && $this->isCollapsed($ex->getFile())) { - foreach ($stack as $key => $row) { - if (isset($row['file']) && !$this->isCollapsed($row['file'])) { $expanded = $key; break; } - } - } ?> - -getCode() ? ' #' . $ex->getCode() : ''), ENT_NOQUOTES, 'UTF-8') ?>
- -getMessage(), ENT_IGNORE, 'UTF-8') ?>
--- - - - -Source file
- --File: getFile(), $ex->getLine()) ?>
- getFile())): ?>getFile(), $ex->getLine(), 15, $ex instanceof \ErrorException && isset($ex->context) ? $ex->context : NULL) ?> --- - - - context) && is_array($ex->context)):?> -Call stack
- --- $row): ?> -
-- - -
- - - - - inner-code - - - source - - "; - if (isset($row['class'])) echo htmlspecialchars($row['class'] . $row['type'], ENT_NOQUOTES, 'UTF-8'); - if (isset($row['object'])) echo ''; - echo htmlspecialchars($row['function'], ENT_NOQUOTES, 'UTF-8'), '('; - if (!empty($row['args'])): ?>arguments) -
- - - - - - -TRUE)) ?>- - - --- -- getParameters(); - } catch (\Exception $e) { - $params = array(); - } - foreach ($row['args'] as $k => $v) { - echo '
-\n"; - } - ?> - ', htmlspecialchars(isset($params[$k]) ? '$' . $params[$k]->name : "#$k", ENT_IGNORE, 'UTF-8'), ' '; - echo Dumper::toHtml($v, array(Dumper::LOCATION => Dumper::LOCATION_CLASS, Dumper::LIVE => TRUE)); - echo " -- - - getPrevious()); ?> -Variables
- ----- context as $k => $v) { - echo '
-\n"; - } - ?> - $', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', Dumper::toHtml($v, array(Dumper::LOCATION => Dumper::LOCATION_CLASS, Dumper::LIVE => TRUE)), " -- - - - - - - - - - - -Exception
-- Dumper::LOCATION_CLASS, Dumper::LIVE => TRUE)) ?> --- - -Environment
- --$_SERVER
--- - -- $v) echo '
-\n"; - ?> - ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', Dumper::toHtml($v), " $_SESSION
-- -- - - -empty
- -- $v) echo '
- -\n"; - ?> - ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', $k === '__NF' ? 'Nette Session' : Dumper::toHtml($v, array(Dumper::LOCATION => Dumper::LOCATION_CLASS, Dumper::LIVE => TRUE)), " Nette Session
--- - - - -- $v) echo '
-\n"; - ?> - ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', Dumper::toHtml($v, array(Dumper::LOCATION => Dumper::LOCATION_CLASS, Dumper::LIVE => TRUE)), " Constants
--- - - -- $v) { - echo '
-\n"; - } - ?> - ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' '; - echo '', Dumper::toHtml($v), " Included files ()
--- - --
-', htmlspecialchars($v, ENT_IGNORE, 'UTF-8'), " \n"; - } - ?> -Configuration options
-- |.+\z#s', '', ob_get_clean()) // @ phpinfo can be disabled ?> ---- - -HTTP request
- -- -Headers
--- - - - -- $v) echo '
-\n"; - ?> - ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', htmlspecialchars($v, ENT_IGNORE, 'UTF-8'), " $
- -empty
- --- - -- $v) echo '
-\n"; - ?> - ', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), ' ', Dumper::toHtml($v), " -- - - - - - - - - - -HTTP response
- --Headers
- -'; - ?>- -no headers
- -+ ++ + + + + + ++diff -Nru php-nette-2.3.10/Nette/Tracy/assets/BlueScreen/page.phtml php-nette-2.4-20160731/Nette/Tracy/assets/BlueScreen/page.phtml --- php-nette-2.3.10/Nette/Tracy/assets/BlueScreen/page.phtml 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/BlueScreen/page.phtml 2016-07-31 17:47:04.000000000 +0000 @@ -0,0 +1,56 @@ +getCode() ? ' #' . $exception->getCode() : ''; + +?>+ getMessage()): ?>+ + getPrevious()): ?> + + + + + + + + += Helpers::escapeHtml($title . $code) ?>
+ + += Helpers::escapeHtml(trim($exception->getMessage() ?: $title . $code)) ?> + getMessage())) ?>" target="_blank" rel="noreferrer noopener">search► + skip error►
+> ++Caused by
+ ++' ?> + + + count((array) new \Exception)):?> +++ + + + renderPanels($ex) as $panel): ?> += Helpers::escapeHtml(Helpers::getClass($ex) . ($ex->getCode() ? ' #' . $ex->getCode() : '')) ?>
+ += Helpers::escapeHtml($ex->getMessage()) ?>
+++ + + + getTrace(); $expanded = NULL ?> + getSeverity(), [E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED])) && $this->isCollapsed($ex->getFile())) { + foreach ($stack as $key => $row) { + if (isset($row['file']) && !$this->isCollapsed($row['file'])) { $expanded = $key; break; } + } + } ?> + += Helpers::escapeHtml($panel->tab) ?>
+ ++ = $panel->panel ?> +++ + + + +Source file
+ ++File: = Helpers::editorLink($ex->getFile(), $ex->getLine()) ?>
+ getFile())): ?>= self::highlightFile($ex->getFile(), $ex->getLine(), 15, $ex instanceof \ErrorException && isset($ex->context) ? $ex->context : NULL) ?> +++ + + + context) && is_array($ex->context)):?> +Call stack
+ +++ $row): ?> +
+- + +
+ + + = Helpers::editorLink($row['file'], $row['line']) ?> + + inner-code + + + source + + "; + if (isset($row['class'])) echo Helpers::escapeHtml($row['class'] . $row['type']); + if (isset($row['object'])) echo ''; + echo Helpers::escapeHtml($row['function']), '('; + if (!empty($row['args'])): ?>arguments) +
+ + + + + + += $dump($row['object']) ?>+ + + +++ ++ getParameters(); + } catch (\Exception $e) { + $params = []; + } + foreach ($row['args'] as $k => $v) { + echo '
+\n"; + } + ?> + ', Helpers::escapeHtml(isset($params[$k]) ? '$' . $params[$k]->name : "#$k"), ' '; + echo $dump($v); + echo " ++ + + getPrevious()); ?> +Variables
+ +++++ context as $k => $v) { + echo '
+\n"; + } + ?> + $', Helpers::escapeHtml($k), ' ', $dump($v), " ++ + + + +Exception
++ = $dump($exception) ?> +++ + + + + renderPanels(NULL) as $panel): ?> + bottom)) { $bottomPanels[] = $panel; continue; } ?> +Last muted error
++ += Helpers::errorTypeToString($lastError['type']) ?>: = Helpers::escapeHtml($lastError['message']) ?>
+ += Helpers::editorLink($lastError['file'], $lastError['line']) ?>
+= self::highlightFile($lastError['file'], $lastError['line']) ?>+ +inner-code
+ + +++ + + += Helpers::escapeHtml($panel->tab) ?>
+ ++ = $panel->panel ?> +++ + +Environment
+ ++$_SERVER
+++ + ++ $v) echo '
+\n"; + ?> + ', Helpers::escapeHtml($k), ' ', $dump($v), " $_SESSION
++ ++ + + +empty
+ ++ $v) echo '
+ +\n"; + ?> + ', Helpers::escapeHtml($k), ' ', $k === '__NF' ? 'Nette Session' : $dump($v), " Nette Session
+++ + + + ++ $v) echo '
+\n"; + ?> + ', Helpers::escapeHtml($k), ' ', $dump($v), " Constants
+++ + + ++ $v) { + echo '
+\n"; + } + ?> + ', Helpers::escapeHtml($k), ' '; + echo '', $dump($v), " Configuration options
++ |.+\z#s', '', ob_get_clean()) // @ phpinfo can be disabled ?> ++++ + +HTTP request
+ ++ +Headers
+++ + + + ++ $v) echo '
+\n"; + ?> + ', Helpers::escapeHtml($k), ' ', Helpers::escapeHtml($v), " $= Helpers::escapeHtml($name) ?>
+ +empty
+ +++ + ++ $v) echo '
+\n"; + ?> + ', Helpers::escapeHtml($k), ' ', $dump($v), " ++ + + +HTTP response
+ ++Headers
+ +'; + ?>+ +no headers
+ +++ + + += Helpers::escapeHtml($panel->tab) ?>
+ ++ = $panel->panel ?> ++
+ + +- Report generated at = @date('Y/m/d H:i:s') // @ timezone may not be set ?>
+- = Helpers::escapeHtml($source) ?>
+- = Helpers::escapeHtml($item) ?>
+= Helpers::escapeHtml($title . ': ' . $exception->getMessage() . $code) ?> + getPrevious()): ?> + + + + + + + + + + + + + + + + diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Dumper/dumper.css php-nette-2.4-20160731/Nette/Tracy/assets/Dumper/dumper.css --- php-nette-2.3.10/Nette/Tracy/assets/Dumper/dumper.css 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Dumper/dumper.css 2016-07-31 17:47:04.000000000 +0000 @@ -1,70 +1 @@ -/** - * This file is part of the Tracy (https://tracy.nette.org) - */ - -/* toggle */ -.tracy-collapsed { - display: none; -} - -.tracy-toggle.tracy-collapsed { - display: inline; -} - -.tracy-toggle { - cursor: pointer; -} - -.tracy-toggle:after { - content: " ▼"; - opacity: .4; -} - -.tracy-toggle.tracy-collapsed:after { - content: " ►"; -} - - -/* dump */ -pre.tracy-dump { - text-align: left; - color: #444; - background: white; -} - -pre.tracy-dump div { - padding-left: 3ex; -} - -pre.tracy-dump div div { - border-left: 1px solid rgba(0, 0, 0, .1); - margin-left: .5ex; -} - -.tracy-dump-array, .tracy-dump-object { - color: #C22; -} - -.tracy-dump-string { - color: #35D; -} - -.tracy-dump-number { - color: #090; -} - -.tracy-dump-null, .tracy-dump-bool { - color: #850; -} - -.tracy-dump-visibility, .tracy-dump-hash { - font-size: 85%; color: #999; -} - -.tracy-dump-indent { - display: none; -} - -span[data-tracy-href] { - border-bottom: 1px dotted rgba(0, 0, 0, .2); -} +pre.tracy-dump{text-align:left;color:#444;background:white}pre.tracy-dump div{padding-left:3ex}pre.tracy-dump div div{border-left:1px solid rgba(0,0,0,.1);margin-left:.5ex}.tracy-dump-array,.tracy-dump-object{color:#C22}.tracy-dump-string{color:#35D}.tracy-dump-number{color:#090}.tracy-dump-null,.tracy-dump-bool{color:#850}.tracy-dump-visibility,.tracy-dump-hash{font-size:85%;color:#999}.tracy-dump-indent{display:none}span[data-tracy-href]{border-bottom:1px dotted rgba(0,0,0,.2)} \ No newline at end of file diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Dumper/dumper.js php-nette-2.4-20160731/Nette/Tracy/assets/Dumper/dumper.js --- php-nette-2.3.10/Nette/Tracy/assets/Dumper/dumper.js 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Dumper/dumper.js 2016-07-31 17:47:04.000000000 +0000 @@ -1,207 +1,6 @@ -/** - * This file is part of the Tracy (https://tracy.nette.org) - */ - -(function() { - var COLLAPSE_COUNT = 7, - COLLAPSE_COUNT_TOP = 14; - - Tracy = window.Tracy || {}; - - Tracy.Dumper = Tracy.Dumper || {}; - - Tracy.Dumper.init = function(repository) { - if (repository) { - [].forEach.call(document.querySelectorAll('.tracy-dump[data-tracy-dump]'), function(el) { - try { - el.appendChild(build(JSON.parse(el.getAttribute('data-tracy-dump')), repository, el.classList.contains('tracy-collapsed'))); - el.classList.remove('tracy-collapsed'); - el.removeAttribute('data-tracy-dump'); - } catch (e) { - if (!(e instanceof UnknownEntityException)) { - throw e; - } - } - }); - } - - if (this.inited) { - return; - } - this.inited = true; - - document.body.addEventListener('click', function(e) { - var link; - - // enables & ctrl key - if (e.ctrlKey && (link = closest(e.target, '[data-tracy-href]'))) { - location.href = link.getAttribute('data-tracy-href'); - return false; - } - - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) { - return; - } - - // enables or toggling - if (link = closest(e.target, '.tracy-toggle')) { - var collapsed = link.classList.contains('tracy-collapsed'), - ref = link.getAttribute('data-tracy-ref') || link.getAttribute('href', 2), - dest = link; - - if (!ref || ref === '#') { - ref = '+'; - } else if (ref.substr(0, 1) === '#') { - dest = document; - } - ref = ref.match(/(\^\s*([^+\s]*)\s*)?(\+\s*(\S*)\s*)?(.*)/); - dest = ref[1] ? closest(dest.parentNode, ref[2]) : dest; - dest = ref[3] ? closest(dest.nextElementSibling, ref[4], 'nextElementSibling') : dest; - dest = ref[5] ? dest.querySelector(ref[5]) : dest; - - link.classList.toggle('tracy-collapsed', !collapsed); - dest.classList.toggle('tracy-collapsed', !collapsed); - e.preventDefault(); - } - }); - }; - - - var build = function(data, repository, collapsed, parentIds) { - var type = data === null ? 'null' : typeof data, - collapseCount = typeof collapsed === 'undefined' ? COLLAPSE_COUNT_TOP : COLLAPSE_COUNT; - - if (type === 'null' || type === 'string' || type === 'number' || type === 'boolean') { - data = type === 'string' ? '"' + data + '"' : (data + '').toUpperCase(); - return createEl(null, null, [ - createEl( - 'span', - {'class': 'tracy-dump-' + type.replace('ean', '')}, - [data + '\n'] - ) - ]); - - } else if (Array.isArray(data)) { - return buildStruct([ - createEl('span', {'class': 'tracy-dump-array'}, ['array']), - ' (' + (data[0] && data.length || '') + ')' - ], - ' [ ... ]', - data[0] === null ? null : data, - collapsed === true || data.length >= collapseCount, - repository, - parentIds - ); - - } else if (type === 'object' && data.number) { - return createEl(null, null, [ - createEl('span', {'class': 'tracy-dump-number'}, [data.number + '\n']) - ]); - - } else if (type === 'object' && data.type) { - return createEl(null, null, [ - createEl('span', null, [data.type + '\n']) - ]); - - } else if (type === 'object') { - var id = data.object || data.resource, - object = repository[id]; - - if (!object) { - throw new UnknownEntityException; - } - parentIds = parentIds || []; - recursive = parentIds.indexOf(id) > -1; - parentIds.push(id); - - return buildStruct([ - createEl('span', { - 'class': data.object ? 'tracy-dump-object' : 'tracy-dump-resource', - title: object.editor ? 'Declared in file ' + object.editor.file + ' on line ' + object.editor.line : null, - 'data-tracy-href': object.editor ? object.editor.url : null - }, [object.name]), - ' ', - createEl('span', {'class': 'tracy-dump-hash'}, ['#' + id]) - ], - ' { ... }', - object.items, - collapsed === true || recursive || (object.items && object.items.length >= collapseCount), - repository, - parentIds - ); - } - }; - - - var buildStruct = function(span, ellipsis, items, collapsed, repository, parentIds) { - var res, toggle, div, handler; - - if (!items || !items.length) { - span.push(!items || items.length ? ellipsis + '\n' : '\n'); - return createEl(null, null, span); - } - - res = createEl(null, null, [ - toggle = createEl('span', {'class': collapsed ? 'tracy-toggle tracy-collapsed' : 'tracy-toggle'}, span), - '\n', - div = createEl('div', {'class': collapsed ? 'tracy-collapsed' : ''}) - ]); - - if (collapsed) { - toggle.addEventListener('click', handler = function() { - toggle.removeEventListener('click', handler); - createItems(div, items, repository, parentIds); - }); - } else { - createItems(div, items, repository, parentIds); - } - return res; - }; - - - var createEl = function(el, attrs, content) { - if (!(el instanceof Node)) { - el = el ? document.createElement(el) : document.createDocumentFragment(); - } - for (var id in attrs || {}) { - if (attrs[id] !== null) { - el.setAttribute(id, attrs[id]); - } - } - content = content || []; - for (id = 0; id < content.length; id++) { - var child = content[id]; - if (child !== null) { - el.appendChild(child instanceof Node ? child : document.createTextNode(child)); - } - } - return el; - }; - - - var createItems = function(el, items, repository, parentIds) { - for (var i = 0; i < items.length; i++) { - var vis = items[i][2]; - createEl(el, null, [ - createEl('span', {'class': 'tracy-dump-key'}, [items[i][0]]), - vis ? ' ' : null, - vis ? createEl('span', {'class': 'tracy-dump-visibility'}, [vis === 1 ? 'protected' : 'private']) : null, - ' => ', - build(items[i][1], repository, null, parentIds) - ]); - } - }; - - var UnknownEntityException = function() {}; - - - // finds closing maching element - var closest = function(el, selector, func) { - var matches = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector; - while (el && selector && !(el.nodeType === 1 && matches.call(el, selector))) { - el = el[func || 'parentNode']; - } - return el; - }; - -})(); +(function(){Tracy=window.Tracy||{};Tracy.Dumper=Tracy.Dumper||{};Tracy.Dumper.init=function(a,e){a&&[].forEach.call((e||document).querySelectorAll(".tracy-dump[data-tracy-dump]"),function(c){try{c.appendChild(k(JSON.parse(c.getAttribute("data-tracy-dump")),a,c.classList.contains("tracy-collapsed"))),c.classList.remove("tracy-collapsed"),c.removeAttribute("data-tracy-dump")}catch(b){if(!(b instanceof m))throw b;}});this.inited||(this.inited=!0,document.body.addEventListener("click",function(a){var b; +if(a.ctrlKey&&(b=Tracy.closest(a.target,"[data-tracy-href]")))return location.href=b.getAttribute("data-tracy-href"),!1}),Tracy.Toggle.init())};var k=function(a,e,c,b){var d=null===a?"null":typeof a,h="undefined"===typeof c?14:7;if("null"===d||"string"===d||"number"===d||"boolean"===d)return a="string"===d?'"'+a+'"':(a+"").toUpperCase(),f(null,null,[f("span",{"class":"tracy-dump-"+d.replace("ean","")},[a+"\n"])]);if(Array.isArray(a))return n([f("span",{"class":"tracy-dump-array"},["array"])," ("+ +(a[0]&&a.length||"")+")"]," [ ... ]",null===a[0]?null:a,!0===c||a.length>=h,e,b);if("object"===d&&a.number)return f(null,null,[f("span",{"class":"tracy-dump-number"},[a.number+"\n"])]);if("object"===d&&a.type)return f(null,null,[f("span",null,[a.type+"\n"])]);if("object"===d){var d=a.object||a.resource,g=e[d];if(!g)throw new m;b=b||[];recursive=-1=h,e,b)}},n=function(a,e,c,b,d,h){var g,l,k;if(!c||!c.length)return a.push(!c||c.length?e+"\n":"\n"),f(null,null,a);a=f(null,null,[g=f("span",{"class":b?"tracy-toggle tracy-collapsed":"tracy-toggle"},a),"\n",l=f("div",{"class":b?"tracy-collapsed":""})]);b?g.addEventListener("tracy-toggle",k=function(){g.removeEventListener("tracy-toggle", +k);p(l,c,d,h)}):p(l,c,d,h);return a},f=function(a,e,c){a instanceof Node||(a=a?document.createElement(a):document.createDocumentFragment());for(var b in e||{})null!==e[b]&&a.setAttribute(b,e[b]);c=c||[];for(b=0;b ') { @@ -152,7 +232,7 @@ foreach ($source as $n => $s) { $spans += substr_count($s, ']+>#', $s, $tags); if ($n == $line) { $out .= sprintf( diff -Nru php-nette-2.3.10/Nette/Tracy/Debugger.php php-nette-2.4-20160731/Nette/Tracy/Debugger.php --- php-nette-2.3.10/Nette/Tracy/Debugger.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/Debugger.php 2016-07-31 17:46:44.000000000 +0000 @@ -16,9 +16,9 @@ */ class Debugger { - const VERSION = '2.3.10'; + const VERSION = '2.4.2'; - /** server modes {@link Debugger::enable()} */ + /** server modes for Debugger::enable() */ const DEVELOPMENT = FALSE, PRODUCTION = TRUE, @@ -26,16 +26,13 @@ const COOKIE_SECRET = 'tracy-debug'; - /** @deprecated */ - public static $version = self::VERSION; - /** @var bool in production mode is suppressed any debugging output */ public static $productionMode = self::DETECT; /** @var bool whether to display debug bar in development mode */ public static $showBar = TRUE; - /** @var bool {@link Debugger::enable()} */ + /** @var bool */ private static $enabled = FALSE; /** @var string reserved memory; also prevents double rendering */ @@ -46,26 +43,29 @@ /********************* errors and exceptions reporting ****************d*g**/ - /** @var bool|int determines whether any error will cause immediate death; if integer that it's matched against error severity */ + /** @var bool|int determines whether any error will cause immediate death in development mode; if integer that it's matched against error severity */ public static $strictMode = FALSE; /** @var bool disables the @ (shut-up) operator so that notices and warnings are no longer hidden */ public static $scream = FALSE; /** @var array of callables specifies the functions that are automatically called after fatal error */ - public static $onFatalError = array(); + public static $onFatalError = []; /********************* Debugger::dump() ****************d*g**/ - /** @var int how many nested levels of array/object properties display {@link Debugger::dump()} */ + /** @var int how many nested levels of array/object properties display by dump() */ public static $maxDepth = 3; - /** @var int how long strings display {@link Debugger::dump()} */ - public static $maxLen = 150; + /** @var int how long strings display by dump() */ + public static $maxLength = 150; - /** @var bool display location? {@link Debugger::dump()} */ + /** @var bool display location by dump()? */ public static $showLocation = FALSE; + /** @deprecated */ + public static $maxLen = 150; + /********************* logging ****************d*g**/ /** @var string name of the directory where errors should be logged */ @@ -77,7 +77,7 @@ /** @var string|array email(s) to which send error notifications */ public static $email; - /** {@link Debugger::log()} and {@link Debugger::fireLog()} */ + /** for Debugger::log() and Debugger::fireLog() */ const DEBUG = ILogger::DEBUG, INFO = ILogger::INFO, @@ -91,12 +91,12 @@ /** @var int timestamp with microseconds of the start of the request */ public static $time; - /** @deprecated */ - public static $source; - /** @var string URI pattern mask to open editor */ public static $editor = 'editor://open/?file=%file&line=%line'; + /** @var array replacements in path */ + public static $editorMapping = []; + /** @var string command to open browser (use 'start ""' in Windows) */ public static $browser; @@ -143,6 +143,7 @@ self::$productionMode = is_bool($mode) ? $mode : !self::detectDebugMode($mode); } + self::$maxLen = & self::$maxLength; self::$reserved = str_repeat('t', 3e5); self::$time = isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime(TRUE); self::$obLevel = ob_get_level(); @@ -173,17 +174,61 @@ ) { self::exceptionHandler(new \RuntimeException("Unable to set 'display_errors' because function ini_set() is disabled.")); } - error_reporting(E_ALL | E_STRICT); + error_reporting(E_ALL); + + if (self::$enabled) { + return; + } + self::$enabled = TRUE; + + register_shutdown_function([__CLASS__, 'shutdownHandler']); + set_exception_handler([__CLASS__, 'exceptionHandler']); + set_error_handler([__CLASS__, 'errorHandler']); + + array_map('class_exists', ['Tracy\Bar', 'Tracy\BlueScreen', 'Tracy\DefaultBarPanel', 'Tracy\Dumper', + 'Tracy\FireLogger', 'Tracy\Helpers', 'Tracy\Logger']); + + if (self::$productionMode) { + + } elseif (headers_sent($file, $line) || ob_get_length()) { + throw new \LogicException( + __METHOD__ . '() called after some output has been sent. ' + . ($file ? "Output started at $file:$line." : 'Try Tracy\OutputDebugger to find where output started.') + ); + + } elseif (self::getBar()->dispatchAssets()) { + exit; + + } elseif (session_status() === PHP_SESSION_ACTIVE) { + self::dispatch(); + } + } + - if (!self::$enabled) { - register_shutdown_function(array(__CLASS__, 'shutdownHandler')); - set_exception_handler(array(__CLASS__, 'exceptionHandler')); - set_error_handler(array(__CLASS__, 'errorHandler')); + /** + * @return void + */ + public static function dispatch() + { + if (self::$productionMode) { + return; - array_map('class_exists', array('Tracy\Bar', 'Tracy\BlueScreen', 'Tracy\DefaultBarPanel', 'Tracy\Dumper', - 'Tracy\FireLogger', 'Tracy\Helpers', 'Tracy\Logger')); + } elseif (headers_sent($file, $line) || ob_get_length()) { + throw new \LogicException( + __METHOD__ . '() called after some output has been sent. ' + . ($file ? "Output started at $file:$line." : 'Try Tracy\OutputDebugger to find where output started.') + ); - self::$enabled = TRUE; + } elseif (session_status() !== PHP_SESSION_ACTIVE) { + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '1'); + ini_set('session.use_trans_sid', '0'); + ini_set('session.cookie_path', '/'); + ini_set('session.cookie_httponly', '1'); + session_start(); + } + if (self::getBar()->dispatchContent()) { + exit; } } @@ -209,13 +254,13 @@ } $error = error_get_last(); - if (in_array($error['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_RECOVERABLE_ERROR, E_USER_ERROR), TRUE)) { + if (in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_RECOVERABLE_ERROR, E_USER_ERROR], TRUE)) { self::exceptionHandler( Helpers::fixStack(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])), FALSE ); - } elseif (self::$showBar && !connection_aborted() && !self::$productionMode && self::isHtmlMode()) { + } elseif (self::$showBar && !self::$productionMode) { self::$reserved = NULL; self::removeOutputBuffers(FALSE); self::getBar()->render(); @@ -240,7 +285,7 @@ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; $code = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE ? 503 : 500; header("$protocol $code", TRUE, $code); - if (self::isHtmlMode()) { + if (Helpers::isHtmlMode()) { header('Content-Type: text/html; charset=UTF-8'); } } @@ -255,7 +300,7 @@ } catch (\Exception $e) { } - if (self::isHtmlMode()) { + if (Helpers::isHtmlMode()) { $logged = empty($e); require self::$errorTemplate ?: __DIR__ . '/assets/Debugger/error.500.phtml'; } elseif (PHP_SAPI === 'cli') { @@ -263,7 +308,7 @@ . (isset($e) ? "Unable to log error.\n" : "Error was logged.\n")); } - } elseif (!connection_aborted() && self::isHtmlMode()) { + } elseif (!connection_aborted() && (Helpers::isHtmlMode() || Helpers::isAjax())) { self::getBlueScreen()->render($exception); if (self::$showBar) { self::getBar()->render(); @@ -307,7 +352,7 @@ } if ($exit) { - exit($exception instanceof \Error ? 255 : 254); + exit(255); } } @@ -321,11 +366,11 @@ public static function errorHandler($severity, $message, $file, $line, $context) { if (self::$scream) { - error_reporting(E_ALL | E_STRICT); + error_reporting(E_ALL); } if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) { - if (Helpers::findTrace(debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE), '*::__toString')) { + if (Helpers::findTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), '*::__toString')) { $previous = isset($context['e']) && ($context['e'] instanceof \Exception || $context['e'] instanceof \Throwable) ? $context['e'] : NULL; $e = new ErrorException($message, 0, $severity, $file, $line, $previous); $e->context = $context; @@ -374,25 +419,16 @@ } else { self::fireLog(new ErrorException($message, 0, $severity, $file, $line)); - return self::isHtmlMode() ? NULL : FALSE; // FALSE calls normal error handler + return Helpers::isHtmlMode() || Helpers::isAjax() ? NULL : FALSE; // FALSE calls normal error handler } } - private static function isHtmlMode() - { - return empty($_SERVER['HTTP_X_REQUESTED_WITH']) - && PHP_SAPI !== 'cli' - && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list())); - } - - private static function removeOutputBuffers($errorOccurred) { while (ob_get_level() > self::$obLevel) { - $tmp = ob_get_status(TRUE); - $status = end($tmp); - if (in_array($status['name'], array('ob_gzhandler', 'zlib output compression'))) { + $status = ob_get_status(); + if (in_array($status['name'], ['ob_gzhandler', 'zlib output compression'])) { break; } $fnc = $status['chunk_size'] || !$errorOccurred ? 'ob_end_flush' : 'ob_end_clean'; @@ -413,11 +449,11 @@ { if (!self::$blueScreen) { self::$blueScreen = new BlueScreen; - self::$blueScreen->info = array( + self::$blueScreen->info = [ 'PHP ' . PHP_VERSION, isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL, 'Tracy ' . self::VERSION, - ); + ]; } return self::$blueScreen; } @@ -487,18 +523,18 @@ { if ($return) { ob_start(function () {}); - Dumper::dump($var, array( + Dumper::dump($var, [ Dumper::DEPTH => self::$maxDepth, - Dumper::TRUNCATE => self::$maxLen, - )); + Dumper::TRUNCATE => self::$maxLength, + ]); return ob_get_clean(); } elseif (!self::$productionMode) { - Dumper::dump($var, array( + Dumper::dump($var, [ Dumper::DEPTH => self::$maxDepth, - Dumper::TRUNCATE => self::$maxLen, + Dumper::TRUNCATE => self::$maxLength, Dumper::LOCATION => self::$showLocation, - )); + ]); } return $var; @@ -512,7 +548,7 @@ */ public static function timer($name = NULL) { - static $time = array(); + static $time = []; $now = microtime(TRUE); $delta = isset($time[$name]) ? $now - $time[$name] : 0; $time[$name] = $now; @@ -533,13 +569,13 @@ if (!self::$productionMode) { static $panel; if (!$panel) { - self::getBar()->addPanel($panel = new DefaultBarPanel('dumps')); + self::getBar()->addPanel($panel = new DefaultBarPanel('dumps'), 'Tracy:dumps'); } - $panel->data[] = array('title' => $title, 'dump' => Dumper::toHtml($var, (array) $options + array( + $panel->data[] = ['title' => $title, 'dump' => Dumper::toHtml($var, (array) $options + [ Dumper::DEPTH => self::$maxDepth, - Dumper::TRUNCATE => self::$maxLen, + Dumper::TRUNCATE => self::$maxLength, Dumper::LOCATION => self::$showLocation ?: Dumper::LOCATION_CLASS | Dumper::LOCATION_SOURCE, - ))); + ])]; } return $var; } diff -Nru php-nette-2.3.10/Nette/Tracy/Dumper.php php-nette-2.4-20160731/Nette/Tracy/Dumper.php --- php-nette-2.3.10/Nette/Tracy/Dumper.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/Dumper.php 2016-07-31 17:46:44.000000000 +0000 @@ -25,12 +25,12 @@ LIVE = 'live'; // will be rendered using JavaScript const - LOCATION_SOURCE = 1, // shows where dump was called - LOCATION_LINK = 2, // appends clickable anchor - LOCATION_CLASS = 4; // shows where class is defined + LOCATION_SOURCE = 0b0001, // shows where dump was called + LOCATION_LINK = 0b0010, // appends clickable anchor + LOCATION_CLASS = 0b0100; // shows where class is defined /** @var array */ - public static $terminalColors = array( + public static $terminalColors = [ 'bool' => '1;33', 'null' => '1;33', 'number' => '1;32', @@ -41,28 +41,28 @@ 'visibility' => '1;30', 'resource' => '1;37', 'indent' => '1;30', - ); + ]; /** @var array */ - public static $resources = array( + public static $resources = [ 'stream' => 'stream_get_meta_data', 'stream-context' => 'stream_context_get_options', 'curl' => 'curl_getinfo', - ); + ]; /** @var array */ - public static $objectExporters = array( + public static $objectExporters = [ 'Closure' => 'Tracy\Dumper::exportClosure', 'SplFileInfo' => 'Tracy\Dumper::exportSplFileInfo', 'SplObjectStorage' => 'Tracy\Dumper::exportSplObjectStorage', '__PHP_Incomplete_Class' => 'Tracy\Dumper::exportPhpIncompleteClass', - ); + ]; /** @var string @internal */ public static $livePrefix; /** @var array */ - private static $liveStorage = array(); + private static $liveStorage = []; /** @@ -88,19 +88,19 @@ */ public static function toHtml($var, array $options = NULL) { - $options = (array) $options + array( + $options = (array) $options + [ self::DEPTH => 4, self::TRUNCATE => 150, self::COLLAPSE => 14, self::COLLAPSE_COUNT => 7, self::OBJECT_EXPORTERS => NULL, - ); + ]; $loc = & $options[self::LOCATION]; $loc = $loc === TRUE ? ~0 : (int) $loc; $options[self::OBJECT_EXPORTERS] = (array) $options[self::OBJECT_EXPORTERS] + self::$objectExporters; uksort($options[self::OBJECT_EXPORTERS], function ($a, $b) { - return $b === '' || (class_exists($a, FALSE) && ($rc = new \ReflectionClass($a)) && $rc->isSubclassOf($b)) ? -1 : 1; + return $b === '' || (class_exists($a, FALSE) && is_subclass_of($a, $b)) ? -1 : 1; }); $live = !empty($options[self::LIVE]) && $var && (is_array($var) || is_object($var) || is_resource($var)); @@ -111,7 +111,7 @@ return '",k(e[d][1],c,null,b)])}},m=function(){}})(); \ No newline at end of file diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Toggle/toggle.css php-nette-2.4-20160731/Nette/Tracy/assets/Toggle/toggle.css --- php-nette-2.3.10/Nette/Tracy/assets/Toggle/toggle.css 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Toggle/toggle.css 2016-07-31 17:47:04.000000000 +0000 @@ -0,0 +1 @@ +.tracy-collapsed{display:none}.tracy-toggle.tracy-collapsed{display:inline}.tracy-toggle{cursor:pointer}.tracy-toggle:after{content:" ▼";opacity:.4}.tracy-toggle.tracy-collapsed:after{content:" ►"} \ No newline at end of file diff -Nru php-nette-2.3.10/Nette/Tracy/assets/Toggle/toggle.js php-nette-2.4-20160731/Nette/Tracy/assets/Toggle/toggle.js --- php-nette-2.3.10/Nette/Tracy/assets/Toggle/toggle.js 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/assets/Toggle/toggle.js 2016-07-31 17:47:06.000000000 +0000 @@ -0,0 +1,4 @@ +(function(){Tracy=window.Tracy||{};Tracy.Toggle=Tracy.Toggle||{};Tracy.Toggle.init=function(){document.body.addEventListener("click",function(a){var d=Tracy.closest(a.target,".tracy-toggle");!d||(a.shiftKey||a.altKey||a.ctrlKey||a.metaKey)||Tracy.Toggle.toggle(d)});this.init=function(){}};Tracy.Toggle.toggle=function(a,d){var e=a.classList.contains("tracy-collapsed"),b=a.getAttribute("data-tracy-ref")||a.getAttribute("href",2),c=a;if("undefined"===typeof d)d=e;else if(!d===e)return;b&&"#"!==b?"#"=== +b.substr(0,1)&&(c=document):b="+";b=b.match(/(\^\s*([^+\s]*)\s*)?(\+\s*(\S*)\s*)?(.*)/);c=b[1]?Tracy.closest(c.parentNode,b[2]):c;c=b[3]?Tracy.closest(c.nextElementSibling,b[4],"nextElementSibling"):c;c=b[5]?c.querySelector(b[5]):c;a.classList.toggle("tracy-collapsed",!d);c.classList.toggle("tracy-collapsed",!d);"function"===typeof window.Event?e=new Event("tracy-toggle",{bubbles:!0}):(e=document.createEvent("Event"),e.initEvent("tracy-toggle",!0,!1));a.dispatchEvent(e)};Tracy.Toggle.persist=function(a, +d){var e=[];a.addEventListener("tracy-toggle",function(a){0>e.indexOf(a.target)&&e.push(a.target)});var b=JSON.parse(sessionStorage.getItem("tracy-toggles-"+a.id));b&&!1!==d&&b.forEach(function(c){var b=a,d;for(d in c.path)if(!(b=b.children[c.path[d]]))return;b.textContent===c.text&&Tracy.Toggle.toggle(b,c.show)});window.addEventListener("unload",function(){b=[].map.call(e,function(c){var b={path:[],text:c.textContent,show:!c.classList.contains("tracy-collapsed")};do b.path.unshift([].indexOf.call(c.parentNode.children, +c)),c=c.parentNode;while(c&&c!==a);return b});sessionStorage.setItem("tracy-toggles-"+a.id,JSON.stringify(b))})};Tracy.closest=function(a,d,e){for(var b=a.matches||a.matchesSelector||a.msMatchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.oMatchesSelector;a&&d&&(1!==a.nodeType||!b.call(a,d));)a=a[e||"parentNode"];return a}})(); \ No newline at end of file diff -Nru php-nette-2.3.10/Nette/Tracy/Bar.php php-nette-2.4-20160731/Nette/Tracy/Bar.php --- php-nette-2.3.10/Nette/Tracy/Bar.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/Bar.php 2016-07-31 17:46:44.000000000 +0000 @@ -13,11 +13,11 @@ */ class Bar { - /** @deprecated */ - public $info = array(); - /** @var IBarPanel[] */ - private $panels = array(); + private $panels = []; + + /** @var bool */ + private $dispatched; /** @@ -56,65 +56,170 @@ */ public function render() { - @session_start(); // @ session may be already started or it is not possible to start session - $session = & $_SESSION['__NF']['debuggerbar']; - $redirect = preg_match('#^Location:#im', implode("\n", headers_list())); - if ($redirect) { + $useSession = $this->dispatched && session_status() === PHP_SESSION_ACTIVE; + $redirectQueue = & $_SESSION['_tracy']['redirect']; + + if (!Helpers::isHtmlMode() && !Helpers::isAjax()) { + return; + + } elseif (Helpers::isAjax()) { + $rows[] = (object) ['type' => 'ajax', 'panels' => $this->renderPanels('-ajax')]; + $dumps = Dumper::fetchLiveData(); + $contentId = $useSession ? $_SERVER['HTTP_X_TRACY_AJAX'] . '-ajax' : NULL; + + } elseif (preg_match('#^Location:#im', implode("\n", headers_list()))) { // redirect + $redirectQueue = array_slice((array) $redirectQueue, -10); Dumper::fetchLiveData(); - Dumper::$livePrefix = count($session) . 'p'; + Dumper::$livePrefix = count($redirectQueue) . 'p'; + $redirectQueue[] = [ + 'panels' => $this->renderPanels('-r' . count($redirectQueue)), + 'dumps' => Dumper::fetchLiveData(), + ]; + return; + + } else { + $rows[] = (object) ['type' => 'main', 'panels' => $this->renderPanels()]; + $dumps = Dumper::fetchLiveData(); + foreach (array_reverse((array) $redirectQueue) as $info) { + $rows[] = (object) ['type' => 'redirect', 'panels' => $info['panels']]; + $dumps += $info['dumps']; + } + $redirectQueue = NULL; + $contentId = $useSession ? substr(md5(uniqid('', TRUE)), 0, 10) : NULL; + } + + ob_start(function () {}); + require __DIR__ . '/assets/Bar/panels.phtml'; + require __DIR__ . '/assets/Bar/bar.phtml'; + $content = Helpers::fixEncoding(ob_get_clean()); + + if ($contentId) { + $queue = & $_SESSION['_tracy']['bar']; + $queue = array_slice(array_filter((array) $queue), -5, NULL, TRUE); + $queue[$contentId] = ['content' => $content, 'dumps' => $dumps]; + } + + if (Helpers::isHtmlMode()) { + $stopXdebug = extension_loaded('xdebug') ? ['XDEBUG_SESSION_STOP' => 1, 'XDEBUG_PROFILE' => 0, 'XDEBUG_TRACE' => 0] : []; + $path = isset($_SERVER['REQUEST_URI']) ? explode('?', $_SERVER['REQUEST_URI'])[0] : '/'; + $lpath = strtolower($path); + $script = isset($_SERVER['SCRIPT_NAME']) ? strtolower($_SERVER['SCRIPT_NAME']) : ''; + if ($lpath !== $script) { + $max = min(strlen($lpath), strlen($script)); + for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++); + $path = $i ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) : '/'; + $cookiePath = session_get_cookie_params()['path']; + if (substr($cookiePath, 0, strlen($path)) === $path) { + $path = rtrim($cookiePath, '/') . '/'; + } + } + require __DIR__ . '/assets/Bar/loader.phtml'; } + } + + + /** + * @return array + */ + private function renderPanels($suffix = NULL) + { + set_error_handler(function ($severity, $message, $file, $line) { + if (error_reporting() & $severity) { + throw new \ErrorException($message, 0, $severity, $file, $line); + } + }); $obLevel = ob_get_level(); - $panels = array(); + $panels = []; + foreach ($this->panels as $id => $panel) { - $idHtml = preg_replace('#[^a-z0-9]+#i', '-', $id); + $idHtml = preg_replace('#[^a-z0-9]+#i', '-', $id) . $suffix; try { $tab = (string) $panel->getTab(); $panelHtml = $tab ? (string) $panel->getPanel() : NULL; if ($tab && $panel instanceof \Nette\Diagnostics\IBarPanel) { - $panelHtml = preg_replace('~(["\'.\s#])nette-(debug|inner|collapsed|toggle|toggle-collapsed)(["\'\s])~', '$1tracy-$2$3', $panelHtml); - $panelHtml = str_replace('tracy-toggle-collapsed', 'tracy-toggle tracy-collapsed', $panelHtml); + $e = new \Exception('Support for Nette\Diagnostics\IBarPanel is deprecated'); } - $panels[] = array('id' => $idHtml, 'tab' => $tab, 'panel' => $panelHtml); } catch (\Throwable $e) { } catch (\Exception $e) { } if (isset($e)) { - $panels[] = array( - 'id' => "error-$idHtml", - 'tab' => "Error in $id", - 'panel' => ' Error: ' . $id . '
' - . nl2br(htmlSpecialChars($e, ENT_IGNORE, 'UTF-8')) . '', - ); while (ob_get_level() > $obLevel) { // restore ob-level if broken ob_end_clean(); } + $idHtml = "error-$idHtml"; + $tab = "Error in $id"; + $panelHtml = "Error: $id
" . nl2br(Helpers::escapeHtml($e)) . ''; + unset($e); } + $panels[] = (object) ['id' => $idHtml, 'tab' => $tab, 'panel' => $panelHtml]; } - if ($redirect) { - $session[] = array('panels' => $panels, 'liveData' => Dumper::fetchLiveData()); - return; + restore_error_handler(); + return $panels; + } + + + /** + * Renders debug bar assets. + * @return bool + */ + public function dispatchAssets() + { + $asset = isset($_GET['_tracy_bar']) ? $_GET['_tracy_bar'] : NULL; + if ($asset === 'css') { + header('Content-Type: text/css; charset=utf-8'); + header('Cache-Control: max-age=864000'); + header_remove('Pragma'); + header_remove('Set-Cookie'); + readfile(__DIR__ . '/assets/Bar/bar.css'); + readfile(__DIR__ . '/assets/Toggle/toggle.css'); + readfile(__DIR__ . '/assets/Dumper/dumper.css'); + readfile(__DIR__ . '/assets/BlueScreen/bluescreen.css'); + return TRUE; + + } elseif ($asset === 'js') { + header('Content-Type: text/javascript'); + header('Cache-Control: max-age=864000'); + header_remove('Pragma'); + header_remove('Set-Cookie'); + readfile(__DIR__ . '/assets/Bar/bar.js'); + readfile(__DIR__ . '/assets/Toggle/toggle.js'); + readfile(__DIR__ . '/assets/Dumper/dumper.js'); + readfile(__DIR__ . '/assets/BlueScreen/bluescreen.js'); + return TRUE; } + } - $liveData = Dumper::fetchLiveData(); - foreach (array_reverse((array) $session) as $reqId => $info) { - $panels[] = array( - 'tab' => 'previous', - 'panel' => NULL, - 'previous' => TRUE, - ); - foreach ($info['panels'] as $panel) { - $panel['id'] .= '-' . $reqId; - $panels[] = $panel; + /** + * Renders debug bar content. + * @return bool + */ + public function dispatchContent() + { + $this->dispatched = TRUE; + if (Helpers::isAjax()) { + header('X-Tracy-Ajax: 1'); // session must be already locked + } + if (preg_match('#^content(-ajax)?.(\w+)$#', isset($_GET['_tracy_bar']) ? $_GET['_tracy_bar'] : '', $m)) { + $session = & $_SESSION['_tracy']['bar'][$m[2] . $m[1]]; + header('Content-Type: text/javascript'); + header('Cache-Control: max-age=60'); + header_remove('Set-Cookie'); + if ($session) { + $method = $m[1] ? 'loadAjax' : 'init'; + echo "Tracy.Debug.$method(", json_encode($session['content']), ', ', json_encode($session['dumps']), ');'; + $session = NULL; + } + $session = & $_SESSION['_tracy']['bluescreen'][$m[2]]; + if ($session) { + echo "Tracy.BlueScreen.loadAjax(", json_encode($session['content']), ', ', json_encode($session['dumps']), ');'; + $session = NULL; } - $liveData += $info['liveData']; + return TRUE; } - $session = NULL; - - require __DIR__ . '/assets/Bar/bar.phtml'; } } diff -Nru php-nette-2.3.10/Nette/Tracy/BlueScreen.php php-nette-2.4-20160731/Nette/Tracy/BlueScreen.php --- php-nette-2.3.10/Nette/Tracy/BlueScreen.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/BlueScreen.php 2016-07-31 17:46:44.000000000 +0000 @@ -14,13 +14,19 @@ class BlueScreen { /** @var string[] */ - public $info = array(); + public $info = []; /** @var callable[] */ - private $panels = array(); + private $panels = []; /** @var string[] paths to be collapsed in stack trace (e.g. core libraries) */ - public $collapsePaths = array(); + public $collapsePaths = []; + + /** @var int */ + public $maxDepth = 3; + + /** @var int */ + public $maxLength = 150; public function __construct() @@ -52,7 +58,41 @@ */ public function render($exception) { - $panels = $this->panels; + if (Helpers::isAjax() && session_status() === PHP_SESSION_ACTIVE) { + ob_start(function () {}); + $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/content.phtml'); + $contentId = $_SERVER['HTTP_X_TRACY_AJAX']; + $queue = & $_SESSION['_tracy']['bluescreen']; + $queue = array_slice(array_filter((array) $queue), -5, NULL, TRUE); + $queue[$contentId] = ['content' => ob_get_clean(), 'dumps' => Dumper::fetchLiveData()]; + + } else { + $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/page.phtml'); + } + } + + + /** + * Renders blue screen to file (if file exists, it will not be overwritten). + * @param \Exception|\Throwable + * @param string file path + * @return void + */ + public function renderToFile($exception, $file) + { + if ($handle = @fopen($file, 'x')) { + ob_start(); // double buffer prevents sending HTTP headers in some PHP + ob_start(function ($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096); + $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/page.phtml'); + ob_end_flush(); + ob_end_clean(); + fclose($handle); + } + } + + + private function renderTemplate($exception, $template) + { $info = array_filter($this->info); $source = Helpers::getSource(); $sourceIsUrl = preg_match('#^https?://#', $source); @@ -62,8 +102,48 @@ $skipError = $sourceIsUrl && $exception instanceof \ErrorException && !empty($exception->skippable) ? $source . (strpos($source, '?') ? '&' : '?') . '_tracy_skip_error' : NULL; + $lastError = $exception instanceof \ErrorException || $exception instanceof \Error ? NULL : error_get_last(); + $dump = function($v) { + return Dumper::toHtml($v, [ + Dumper::DEPTH => $this->maxDepth, + Dumper::TRUNCATE => $this->maxLength, + Dumper::LIVE => TRUE, + Dumper::LOCATION => Dumper::LOCATION_CLASS, + ]); + }; - require __DIR__ . '/assets/BlueScreen/bluescreen.phtml'; + require $template; + } + + + /** + * @return \stdClass[] + */ + private function renderPanels($ex) + { + $obLevel = ob_get_level(); + $res = []; + foreach ($this->panels as $callback) { + try { + $panel = call_user_func($callback, $ex); + if (empty($panel['tab']) || empty($panel['panel'])) { + continue; + } + $res[] = (object) $panel; + continue; + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + while (ob_get_level() > $obLevel) { // restore ob-level if broken + ob_end_clean(); + } + is_callable($callback, TRUE, $name); + $res[] = (object) [ + 'tab' => "Error in panel $name", + 'panel' => nl2br(Helpers::escapeHtml($e)), + ]; + } + return $res; } @@ -80,7 +160,7 @@ if ($source) { $source = static::highlightPhp($source, $line, $lines, $vars); if ($editor = Helpers::editorUri($file, $line)) { - $source = substr_replace($source, ' data-tracy-href="' . htmlspecialchars($editor, ENT_QUOTES, 'UTF-8') . '"', 4, 0); + $source = substr_replace($source, ' data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0); } return $source; } @@ -104,7 +184,7 @@ ini_set('highlight.string', '#080'); } - $source = str_replace(array("\r\n", "\r"), "\n", $source); + $source = str_replace(["\r\n", "\r"], "\n", $source); $source = explode("\n", highlight_string($source, TRUE)); $out = $source[0]; //$source = str_replace('
', "\n", $source[1]); @@ -114,14 +194,14 @@ $out = preg_replace_callback('#">\$(\w+)( )?#', function ($m) use ($vars) { return array_key_exists($m[1], $vars) ? '" title="' - . str_replace('"', '"', trim(strip_tags(Dumper::toHtml($vars[$m[1]], array(Dumper::DEPTH => 1))))) + . str_replace('"', '"', trim(strip_tags(Dumper::toHtml($vars[$m[1]], [Dumper::DEPTH => 1])))) . $m[0] : $m[0]; }, $out); } $out = str_replace(' ', ' ', $out); - return ""; + return "$out"; } @@ -135,7 +215,7 @@ $source = explode("\n", "\n" . str_replace("\r\n", "\n", $html)); $out = ''; $spans = 1; - $start = $i = max(1, $line - floor($lines * 2 / 3)); + $start = $i = max(1, min($line, count($source) - 1) - floor($lines * 2 / 3)); while (--$i >= 1) { // find last highlighted block if (preg_match('#.*(?span[^>]*>)#', $source[$i], $m)) { if ($m[1] !== '$out" : '>') + . ($live ? " data-tracy-dump='" . json_encode(self::toJson($var, $options), JSON_HEX_APOS | JSON_HEX_AMP) . "'>" : '>') . ($live ? '' : self::dumpVar($var, $options)) . ($file && $loc & self::LOCATION_LINK ? 'in ' . Helpers::editorLink($file, $line) . '' : '') . "\n"; @@ -135,7 +135,7 @@ public static function toTerminal($var, array $options = NULL) { return htmlspecialchars_decode(strip_tags(preg_replace_callback('#|#', function ($m) { - return "\033[" . (isset($m[1], Dumper::$terminalColors[$m[1]]) ? Dumper::$terminalColors[$m[1]] : '0') . 'm'; + return "\033[" . (isset($m[1], self::$terminalColors[$m[1]]) ? self::$terminalColors[$m[1]] : '0') . 'm'; }, self::toHtml($var, $options))), ENT_QUOTES); } @@ -187,7 +187,7 @@ private static function dumpString(& $var, $options) { return '"' - . htmlspecialchars(self::encodeString($var, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8') + . Helpers::escapeHtml(self::encodeString($var, $options[self::TRUNCATE])) . '"' . (strlen($var) > 1 ? ' (' . strlen($var) . ')' : '') . "\n"; } @@ -215,7 +215,7 @@ $var[$marker] = TRUE; foreach ($var as $k => & $v) { if ($k !== $marker) { - $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . htmlspecialchars(self::encodeString($k, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8') . '"'; + $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . Helpers::escapeHtml(self::encodeString($k, $options[self::TRUNCATE])) . '"'; $out .= ' ' . str_repeat('| ', $level) . '' . '' . $k . ' => ' . self::dumpVar($v, $options, $level + 1); @@ -242,9 +242,9 @@ . ($editor ? Helpers::formatHtml( ' title="Declared in file % on line %" data-tracy-href="%"', $rc->getFileName(), $rc->getStartLine(), $editor ) : '') - . '>' . htmlspecialchars(Helpers::getClass($var)) . ' #' . substr(md5(spl_object_hash($var)), 0, 4) . ''; + . '>' . Helpers::escapeHtml(Helpers::getClass($var)) . ' #' . substr(md5(spl_object_hash($var)), 0, 4) . ''; - static $list = array(); + static $list = []; if (empty($fields)) { return $out . "\n"; @@ -264,7 +264,7 @@ $vis = ' ' . ($k[1] === '*' ? 'protected' : 'private') . ''; $k = substr($k, strrpos($k, "\x00") + 1); } - $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . htmlspecialchars(self::encodeString($k, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8') . '"'; + $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . Helpers::escapeHtml(self::encodeString($k, $options[self::TRUNCATE])) . '"'; $out .= ' ' . str_repeat('| ', $level) . '' . '' . $k . "$vis => " . self::dumpVar($v, $options, $level + 1); @@ -281,13 +281,13 @@ private static function dumpResource(& $var, $options, $level) { $type = get_resource_type($var); - $out = '' . htmlSpecialChars($type, ENT_IGNORE, 'UTF-8') . ' resource ' + $out = '' . Helpers::escapeHtml($type) . ' resource ' . '#' . intval($var) . ''; if (isset(self::$resources[$type])) { $out = "$out\n"; foreach (call_user_func(self::$resources[$type], $var) as $k => $v) { $out .= ' ' . str_repeat('| ', $level) . '' - . '' . htmlSpecialChars($k, ENT_IGNORE, 'UTF-8') . ' => ' . self::dumpVar($v, $options, $level + 1); + . '' . Helpers::escapeHtml($k) . ' => ' . self::dumpVar($v, $options, $level + 1); } return $out . ''; } @@ -305,8 +305,8 @@ } elseif (is_float($var)) { return is_finite($var) - ? (strpos($tmp = json_encode($var), '.') ? $var : array('number' => "$tmp.0")) - : array('type' => (string) $var); + ? (strpos($tmp = json_encode($var), '.') ? $var : ['number' => "$tmp.0"]) + : ['type' => (string) $var]; } elseif (is_string($var)) { return self::encodeString($var, $options[self::TRUNCATE]); @@ -317,14 +317,14 @@ $marker = uniqid("\x00", TRUE); } if (isset($var[$marker]) || $level >= $options[self::DEPTH]) { - return array(NULL); + return [NULL]; } - $res = array(); + $res = []; $var[$marker] = TRUE; foreach ($var as $k => & $v) { if ($k !== $marker) { $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"'; - $res[] = array($k, self::toJson($v, $options, $level + 1)); + $res[] = [$k, self::toJson($v, $options, $level + 1)]; } } unset($var[$marker]); @@ -333,7 +333,7 @@ } elseif (is_object($var)) { $obj = & self::$liveStorage[spl_object_hash($var)]; if ($obj && $obj['level'] <= $level) { - return array('object' => $obj['id']); + return ['object' => $obj['id']]; } if ($options[self::LOCATION] & self::LOCATION_CLASS) { @@ -341,17 +341,17 @@ $editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine()); } static $counter = 1; - $obj = $obj ?: array( + $obj = $obj ?: [ 'id' => self::$livePrefix . '0' . $counter++, // differentiate from resources 'name' => Helpers::getClass($var), - 'editor' => empty($editor) ? NULL : array('file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor), + 'editor' => empty($editor) ? NULL : ['file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor], 'level' => $level, 'object' => $var, - ); + ]; if ($level < $options[self::DEPTH] || !$options[self::DEPTH]) { $obj['level'] = $level; - $obj['items'] = array(); + $obj['items'] = []; foreach (self::exportObject($var, $options[self::OBJECT_EXPORTERS]) as $k => $v) { $vis = 0; @@ -360,26 +360,26 @@ $k = substr($k, strrpos($k, "\x00") + 1); } $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"'; - $obj['items'][] = array($k, self::toJson($v, $options, $level + 1), $vis); + $obj['items'][] = [$k, self::toJson($v, $options, $level + 1), $vis]; } } - return array('object' => $obj['id']); + return ['object' => $obj['id']]; } elseif (is_resource($var)) { $obj = & self::$liveStorage[(string) $var]; if (!$obj) { $type = get_resource_type($var); - $obj = array('id' => self::$livePrefix . (int) $var, 'name' => $type . ' resource'); + $obj = ['id' => self::$livePrefix . (int) $var, 'name' => $type . ' resource']; if (isset(self::$resources[$type])) { foreach (call_user_func(self::$resources[$type], $var) as $k => $v) { - $obj['items'][] = array($k, self::toJson($v, $options, $level + 1)); + $obj['items'][] = [$k, self::toJson($v, $options, $level + 1)]; } } } - return array('resource' => $obj['id']); + return ['resource' => $obj['id']]; } else { - return array('type' => 'unknown type'); + return ['type' => 'unknown type']; } } @@ -387,13 +387,13 @@ /** @return array */ public static function fetchLiveData() { - $res = array(); + $res = []; foreach (self::$liveStorage as $obj) { $id = $obj['id']; unset($obj['level'], $obj['object'], $obj['id']); $res[$id] = $obj; } - self::$liveStorage = array(); + self::$liveStorage = []; return $res; } @@ -421,14 +421,16 @@ } $s = strtr($s, $table); - } elseif ($shortened = ($maxLength && strlen(utf8_decode($s)) > $maxLength)) { + } elseif ($maxLength && $s !== '') { if (function_exists('iconv_substr')) { - $s = iconv_substr($s, 0, $maxLength, 'UTF-8'); + $s = iconv_substr($tmp = $s, 0, $maxLength, 'UTF-8'); + $shortened = $s !== $tmp; } else { $i = $len = 0; do { if (($s[$i] < "\x80" || $s[$i] >= "\xC0") && (++$len > $maxLength)) { $s = substr($s, 0, $i); + $shortened = TRUE; break; } } while (isset($s[++$i])); @@ -459,16 +461,16 @@ private static function exportClosure(\Closure $obj) { $rc = new \ReflectionFunction($obj); - $res = array(); + $res = []; foreach ($rc->getParameters() as $param) { $res[] = '$' . $param->getName(); } - return array( + return [ 'file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'variables' => $rc->getStaticVariables(), 'parameters' => implode(', ', $res), - ); + ]; } @@ -477,7 +479,7 @@ */ private static function exportSplFileInfo(\SplFileInfo $obj) { - return array('path' => $obj->getPathname()); + return ['path' => $obj->getPathname()]; } @@ -486,9 +488,9 @@ */ private static function exportSplObjectStorage(\SplObjectStorage $obj) { - $res = array(); + $res = []; foreach (clone $obj as $item) { - $res[] = array('object' => $item, 'data' => $obj[$item]); + $res[] = ['object' => $item, 'data' => $obj[$item]]; } return $res; } @@ -499,7 +501,7 @@ */ private static function exportPhpIncompleteClass(\__PHP_Incomplete_Class $obj) { - $info = array('className' => NULL, 'private' => array(), 'protected' => array(), 'public' => array()); + $info = ['className' => NULL, 'private' => [], 'protected' => [], 'public' => []]; foreach ((array) $obj as $name => $value) { if ($name === '__PHP_Incomplete_Class_Name') { $info['className'] = $value; @@ -521,7 +523,7 @@ */ private static function findLocation() { - foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) { + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) { if (isset($item['class']) && $item['class'] === __CLASS__) { $location = $item; continue; @@ -543,11 +545,11 @@ if (isset($location['file'], $location['line']) && is_file($location['file'])) { $lines = file($location['file']); $line = $lines[$location['line'] - 1]; - return array( + return [ $location['file'], $location['line'], trim(preg_match('#\w*dump(er::\w+)?\(.*\)#i', $line, $m) ? $m[0] : $line), - ); + ]; } } diff -Nru php-nette-2.3.10/Nette/Tracy/FireLogger.php php-nette-2.4-20160731/Nette/Tracy/FireLogger.php --- php-nette-2.3.10/Nette/Tracy/FireLogger.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/FireLogger.php 2016-07-31 17:46:44.000000000 +0000 @@ -24,7 +24,7 @@ public $maxLength = 150; /** @var array */ - private $payload = array('logs' => array()); + private $payload = ['logs' => []]; /** @@ -38,7 +38,7 @@ return FALSE; } - $item = array( + $item = [ 'name' => 'PHP', 'level' => $priority, 'order' => count($this->payload['logs']), @@ -46,7 +46,7 @@ 'template' => '', 'message' => '', 'style' => 'background:#767ab6', - ); + ]; $args = func_get_args(); if (isset($args[0]) && is_string($args[0])) { @@ -85,23 +85,23 @@ } } - $item['exc_info'] = array('', '', array()); - $item['exc_frames'] = array(); + $item['exc_info'] = ['', '', []]; + $item['exc_frames'] = []; foreach ($trace as $frame) { - $frame += array('file' => NULL, 'line' => NULL, 'class' => NULL, 'type' => NULL, 'function' => NULL, 'object' => NULL, 'args' => NULL); - $item['exc_info'][2][] = array($frame['file'], $frame['line'], "$frame[class]$frame[type]$frame[function]", $frame['object']); + $frame += ['file' => NULL, 'line' => NULL, 'class' => NULL, 'type' => NULL, 'function' => NULL, 'object' => NULL, 'args' => NULL]; + $item['exc_info'][2][] = [$frame['file'], $frame['line'], "$frame[class]$frame[type]$frame[function]", $frame['object']]; $item['exc_frames'][] = $frame['args']; } - if (isset($args[0]) && in_array($args[0], array(self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL), TRUE)) { + if (isset($args[0]) && in_array($args[0], [self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL], TRUE)) { $item['level'] = array_shift($args); } $item['args'] = $args; $this->payload['logs'][] = $this->jsonDump($item, -1); - foreach (str_split(base64_encode(@json_encode($this->payload)), 4990) as $k => $v) { // intentionally @ + foreach (str_split(base64_encode(json_encode($this->payload)), 4990) as $k => $v) { header("FireLogger-de11e-$k:$v"); } return TRUE; @@ -132,7 +132,7 @@ } elseif ($level < $this->maxDepth || !$this->maxDepth) { $var[$marker] = TRUE; - $res = array(); + $res = []; foreach ($var as $k => & $v) { if ($k !== $marker) { $res[$this->jsonDump($k)] = $this->jsonDump($v, $level + 1); @@ -147,13 +147,13 @@ } elseif (is_object($var)) { $arr = (array) $var; - static $list = array(); + static $list = []; if (in_array($var, $list, TRUE)) { return "\xE2\x80\xA6RECURSION\xE2\x80\xA6"; } elseif ($level < $this->maxDepth || !$this->maxDepth) { $list[] = $var; - $res = array("\x00" => '(object) ' . Helpers::getClass($var)); + $res = ["\x00" => '(object) ' . Helpers::getClass($var)]; foreach ($arr as $k => & $v) { if (isset($k[0]) && $k[0] === "\x00") { $k = substr($k, strrpos($k, "\x00") + 1); diff -Nru php-nette-2.3.10/Nette/Tracy/Helpers.php php-nette-2.4-20160731/Nette/Tracy/Helpers.php --- php-nette-2.3.10/Nette/Tracy/Helpers.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/Helpers.php 2016-07-31 17:46:44.000000000 +0000 @@ -20,7 +20,8 @@ */ public static function editorLink($file, $line = NULL) { - if ($editor = self::editorUri($file, $line)) { + $file = strtr($origFile = $file, Debugger::$editorMapping); + if ($editor = self::editorUri($origFile, $line)) { $file = strtr($file, '\\', '/'); if (preg_match('#(^[a-z]:)?/.{1,50}$#i', $file, $m) && strlen($file) > strlen($m[0])) { $file = '...' . $m[0]; @@ -46,7 +47,8 @@ public static function editorUri($file, $line = NULL) { if (Debugger::$editor && $file && is_file($file)) { - return strtr(Debugger::$editor, array('%file' => rawurlencode($file), '%line' => $line ? (int) $line : 1)); + $file = strtr($file, Debugger::$editorMapping); + return strtr(Debugger::$editor, ['%file' => rawurlencode($file), '%line' => $line ? (int) $line : 1]); } } @@ -55,18 +57,24 @@ { $args = func_get_args(); return preg_replace_callback('#%#', function () use (& $args, & $count) { - return htmlspecialchars($args[++$count], ENT_IGNORE | ENT_QUOTES, 'UTF-8'); + return Helpers::escapeHtml($args[++$count]); }, $mask); } + public static function escapeHtml($s) + { + return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + } + + public static function findTrace(array $trace, $method, & $index = NULL) { $m = explode('::', $method); foreach ($trace as $i => $item) { if (isset($item['function']) && $item['function'] === end($m) && isset($item['class']) === isset($m[1]) - && (!isset($item['class']) || $item['class'] === $m[0] || $m[0] === '*' || is_subclass_of($item['class'], $m[0])) + && (!isset($item['class']) || $m[0] === '*' || is_a($item['class'], $m[0], TRUE)) ) { $index = $i; return $item; @@ -80,7 +88,7 @@ */ public static function getClass($obj) { - return current(explode("\x00", get_class($obj))); + return explode("\x00", get_class($obj))[0]; } @@ -88,14 +96,14 @@ public static function fixStack($exception) { if (function_exists('xdebug_get_function_stack')) { - $stack = array(); + $stack = []; foreach (array_slice(array_reverse(xdebug_get_function_stack()), 2, -1) as $row) { - $frame = array( + $frame = [ 'file' => $row['file'], 'line' => $row['line'], 'function' => isset($row['function']) ? $row['function'] : '*unknown*', - 'args' => array(), - ); + 'args' => [], + ]; if (!empty($row['class'])) { $frame['type'] = isset($row['type']) && $row['type'] === 'dynamic' ? '->' : '::'; $frame['class'] = $row['class']; @@ -113,18 +121,14 @@ /** @internal */ public static function fixEncoding($s) { - if (PHP_VERSION_ID < 50400) { - return @iconv('UTF-16', 'UTF-8//IGNORE', iconv('UTF-8', 'UTF-16//IGNORE', $s)); // intentionally @ - } else { - return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES); - } + return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES); } /** @internal */ public static function errorTypeToString($type) { - $types = array( + $types = [ E_ERROR => 'Fatal Error', E_USER_ERROR => 'User Error', E_RECOVERABLE_ERROR => 'Recoverable Error', @@ -140,7 +144,7 @@ E_STRICT => 'Strict standards', E_DEPRECATED => 'Deprecated', E_USER_DEPRECATED => 'User Deprecated', - ); + ]; return isset($types[$type]) ? $types[$type] : 'Unknown error'; } @@ -153,7 +157,8 @@ . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '') . $_SERVER['REQUEST_URI']; } else { - return empty($_SERVER['argv']) ? 'CLI' : 'CLI: ' . implode(' ', $_SERVER['argv']); + return 'CLI (PID: ' . getmypid() . ')' + . (empty($_SERVER['argv']) ? '' : ': ' . implode(' ', $_SERVER['argv'])); } } @@ -165,8 +170,7 @@ if (!$e instanceof \Error && !$e instanceof \ErrorException) { // do nothing } elseif (preg_match('#^Call to undefined function (\S+\\\\)?(\w+)\(#', $message, $m)) { - $funcs = get_defined_functions(); - $funcs = array_merge($funcs['internal'], $funcs['user']); + $funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']); $hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2]); $message .= ", did you mean $hint()?"; @@ -218,4 +222,20 @@ return $best; } + + /** @internal */ + public static function isHtmlMode() + { + return empty($_SERVER['HTTP_X_REQUESTED_WITH']) && empty($_SERVER['HTTP_X_TRACY_AJAX']) + && PHP_SAPI !== 'cli' + && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list())); + } + + + /** @internal */ + public static function isAjax() + { + return isset($_SERVER['HTTP_X_TRACY_AJAX']) && preg_match('#^\w{10}\z#', $_SERVER['HTTP_X_TRACY_AJAX']); + } + } diff -Nru php-nette-2.3.10/Nette/Tracy/Logger.php php-nette-2.4-20160731/Nette/Tracy/Logger.php --- php-nette-2.3.10/Nette/Tracy/Logger.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/Logger.php 2016-07-31 17:46:44.000000000 +0000 @@ -37,7 +37,7 @@ $this->directory = $directory; $this->email = $email; $this->blueScreen = $blueScreen; - $this->mailer = array($this, 'defaultMailer'); + $this->mailer = [$this, 'defaultMailer']; } @@ -69,7 +69,7 @@ $this->logException($message, $exceptionFile); } - if (in_array($priority, array(self::ERROR, self::EXCEPTION, self::CRITICAL), TRUE)) { + if (in_array($priority, [self::ERROR, self::EXCEPTION, self::CRITICAL], TRUE)) { $this->sendEmail($message); } @@ -107,12 +107,12 @@ */ protected function formatLogLine($message, $exceptionFile = NULL) { - return implode(' ', array( + return implode(' ', [ @date('[Y-m-d H-i-s]'), // @ timezone may not be set preg_replace('#\s*\r?\n\s*#', ' ', $this->formatMessage($message)), ' @ ' . Helpers::getSource(), $exceptionFile ? ' @@ ' . basename($exceptionFile) : NULL, - )); + ]); } @@ -141,15 +141,8 @@ protected function logException($exception, $file = NULL) { $file = $file ?: $this->getExceptionFile($exception); - if ($handle = @fopen($file, 'x')) { // @ file may already exist - ob_start(); // double buffer prevents sending HTTP headers in some PHP - ob_start(function ($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096); - $bs = $this->blueScreen ?: new BlueScreen; - $bs->render($exception); - ob_end_flush(); - ob_end_clean(); - fclose($handle); - } + $bs = $this->blueScreen ?: new BlueScreen; + $bs->renderToFile($exception, $file); return $file; } @@ -184,18 +177,18 @@ { $host = preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n')); $parts = str_replace( - array("\r\n", "\n"), - array("\n", PHP_EOL), - array( - 'headers' => implode("\n", array( + ["\r\n", "\n"], + ["\n", PHP_EOL], + [ + 'headers' => implode("\n", [ 'From: ' . ($this->fromEmail ?: "noreply@$host"), 'X-Mailer: Tracy', 'Content-Type: text/plain; charset=UTF-8', 'Content-Transfer-Encoding: 8bit', - )) . "\n", + ]) . "\n", 'subject' => "PHP: An error occurred on the server $host", 'body' => $this->formatMessage($message) . "\n\nsource: " . Helpers::getSource(), - ) + ] ); mail($email, $parts['subject'], $parts['body'], $parts['headers']); diff -Nru php-nette-2.3.10/Nette/Tracy/OutputDebugger.php php-nette-2.4-20160731/Nette/Tracy/OutputDebugger.php --- php-nette-2.3.10/Nette/Tracy/OutputDebugger.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Tracy/OutputDebugger.php 2016-07-31 17:46:44.000000000 +0000 @@ -16,7 +16,7 @@ const BOM = "\xEF\xBB\xBF"; /** @var array of [file, line, output, stack] */ - private $list = array(); + private $list = []; public static function enable() @@ -30,17 +30,17 @@ { foreach (get_included_files() as $file) { if (fread(fopen($file, 'r'), 3) === self::BOM) { - $this->list[] = array($file, 1, self::BOM); + $this->list[] = [$file, 1, self::BOM]; } } - ob_start(array($this, 'handler'), PHP_VERSION_ID >= 50400 ? 1 : 2); + ob_start([$this, 'handler'], 1); } /** @internal */ public function handler($s, $phase) { - $trace = debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); if (isset($trace[0]['file'], $trace[0]['line'])) { $stack = $trace; unset($stack[0]['line'], $stack[0]['args']); @@ -48,7 +48,7 @@ if ($i && $this->list[$i - 1][3] === $stack) { $this->list[$i - 1][2] .= $s; } else { - $this->list[] = array($trace[0]['file'], $trace[0]['line'], $s, $stack); + $this->list[] = [$trace[0]['file'], $trace[0]['line'], $s, $stack]; } } if ($phase === PHP_OUTPUT_HANDLER_FINAL) { @@ -61,15 +61,15 @@ { $res = ''; foreach ($this->list as $item) { - $stack = array(); + $stack = []; foreach (array_slice($item[3], 1) as $t) { - $t += array('class' => '', 'type' => '', 'function' => ''); + $t += ['class' => '', 'type' => '', 'function' => '']; $stack[] = "$t[class]$t[type]$t[function]()" . (isset($t['file'], $t['line']) ? ' in ' . basename($t['file']) . ":$t[line]" : ''); } - $res .= Helpers::editorLink($item[0], $item[1]) . ' ' - . '' + $res .= '' + . Helpers::editorLink($item[0], $item[1]) . ' ' . str_replace(self::BOM, 'BOM', Dumper::toHtml($item[2])) . "
*/ -class Html extends Nette\Object implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString +class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString { + use Nette\SmartObject; + /** @var string element's name */ private $name; @@ -30,20 +32,20 @@ private $isEmpty; /** @var array element's attributes */ - public $attrs = array(); + public $attrs = []; /** @var array of Html | string nodes */ - protected $children = array(); + protected $children = []; /** @var bool use XHTML syntax? */ public static $xhtml = FALSE; /** @var array empty (void) elements */ - public static $emptyElements = array( + public static $emptyElements = [ 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, - ); + ]; /** @@ -55,7 +57,7 @@ public static function el($name = NULL, $attrs = NULL) { $el = new static; - $parts = explode(' ', $name, 2); + $parts = explode(' ', (string) $name, 2); $el->setName($parts[0]); if (is_array($attrs)) { @@ -127,6 +129,68 @@ /** + * Appends value to element's attribute. + * @param string + * @param string|array value to append + * @param string|bool value option + * @return self + */ + public function appendAttribute($name, $value, $option = TRUE) + { + if (is_array($value)) { + $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; + $this->attrs[$name] = $value + $prev; + + } elseif ((string) $value === '') { + $tmp = & $this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists + + } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array + $this->attrs[$name][$value] = $option; + + } else { + $this->attrs[$name] = [$this->attrs[$name] => TRUE, $value => $option]; + } + return $this; + } + + + /** + * Sets element's attribute. + * @param string + * @param mixed + * @return self + */ + public function setAttribute($name, $value) + { + $this->attrs[$name] = $value; + return $this; + } + + + /** + * Returns element's attribute. + * @param string + * @return mixed + */ + public function getAttribute($name) + { + return isset($this->attrs[$name]) ? $this->attrs[$name] : NULL; + } + + + /** + * Unsets element's attribute. + * @param string + * @return self + */ + public function removeAttribute($name) + { + unset($this->attrs[$name]); + return $this; + } + + + /** * Overloaded setter for element's attribute. * @param string HTML attribute name * @param mixed HTML attribute value @@ -175,7 +239,7 @@ * Overloaded setter for element's attribute. * @param string HTML attribute name * @param array (string) HTML attribute value or pair? - * @return self + * @return mixed */ public function __call($m, $args) { @@ -196,14 +260,8 @@ } elseif (count($args) === 1) { // set $this->attrs[$m] = $args[0]; - } elseif ((string) $args[0] === '') { - $tmp = & $this->attrs[$m]; // appending empty value? -> ignore, but ensure it exists - - } elseif (!isset($this->attrs[$m]) || is_array($this->attrs[$m])) { // needs array - $this->attrs[$m][$args[0]] = $args[1]; - - } else { - $this->attrs[$m] = array($this->attrs[$m], $args[0] => $args[1]); + } else { // add + $this->appendAttribute($m, $args[0], $args[1]); } return $this; @@ -219,7 +277,7 @@ public function href($path, $query = NULL) { if ($query) { - $query = http_build_query($query, NULL, '&'); + $query = http_build_query($query, '', '&'); if ($query !== '') { $path .= '?' . $query; } @@ -305,17 +363,39 @@ /** + * @deprecated + */ + public function add($child) + { + trigger_error(__METHOD__ . '() is deprecated, use addHtml() or addText() instead.', E_USER_DEPRECATED); + return $this->addHtml($child); + } + + + /** * Adds new element's child. * @param Html|string Html node or raw HTML string * @return self */ - public function add($child) + public function addHtml($child) { return $this->insert(NULL, $child); } /** + * Appends plain-text string to element content. + * @param string plain-text string + * @return self + */ + public function addText($text) + { + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + return $this->insert(NULL, $text); + } + + + /** * Creates and adds a new Html child. * @param string elements's name * @param array|string element's attributes or raw HTML string @@ -343,7 +423,7 @@ $this->children[] = $child; } else { // insert or replace - array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child)); + array_splice($this->children, (int) $index, $replace ? 1 : 0, [$child]); } } else { @@ -417,7 +497,7 @@ */ public function removeChildren() { - $this->children = array(); + $this->children = []; } @@ -524,6 +604,7 @@ $s = ''; $attrs = $this->attrs; if (isset($attrs['data']) && is_array($attrs['data'])) { // deprecated + trigger_error('Expanded attribute "data" is deprecated.', E_USER_DEPRECATED); foreach ($attrs['data'] as $key => $value) { $attrs['data-' . $key] = $value; } @@ -571,8 +652,8 @@ $q = strpos($value, '"') === FALSE ? '"' : "'"; $s .= ' ' . $key . '=' . $q . str_replace( - array('&', $q, '<'), - array('&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'), + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], $value ) . (strpos($value, '`') !== FALSE && strpbrk($value, ' <>"\'') === FALSE ? ' ' : '') diff -Nru php-nette-2.3.10/Nette/Utils/Image.php php-nette-2.4-20160731/Nette/Utils/Image.php --- php-nette-2.3.10/Nette/Utils/Image.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Image.php 2016-07-31 17:46:44.000000000 +0000 @@ -47,6 +47,7 @@ * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1) * @method void dashedLine($x1, $y1, $x2, $y2, $color) * @method void ellipse($cx, $cy, $w, $h, $color) * @method void fill($x, $y, $color) @@ -56,6 +57,7 @@ * @method void filledRectangle($x1, $y1, $x2, $y2, $color) * @method void fillToBorder($x, $y, $border, $color) * @method void filter($filtertype) + * @method void flip(int $mode) * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = NULL) * @method void gammaCorrect(float $inputgamma, float $outputgamma) * @method int interlace($interlace = NULL) @@ -63,11 +65,13 @@ * @method void layerEffect($effect) * @method void line($x1, $y1, $x2, $y2, $color) * @method void paletteCopy(Image $source) + * @method void paletteToTrueColor() * @method void polygon(array $points, $numPoints, $color) * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = NULL, $tightness = NULL, float $angle = NULL, $antialiasSteps = NULL) * @method void rectangle($x1, $y1, $x2, $y2, $col) * @method Image rotate(float $angle, $backgroundColor) * @method void saveAlpha(bool $saveflag) + * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) * @method void setBrush(Image $brush) * @method void setPixel($x, $y, $color) * @method void setStyle(array $style) @@ -81,22 +85,24 @@ * @property-read int $height * @property-read resource $imageResource */ -class Image extends Nette\Object +class Image { + use Nette\SmartObject; + /** {@link resize()} only shrinks images */ - const SHRINK_ONLY = 1; + const SHRINK_ONLY = 0b0001; /** {@link resize()} will ignore aspect ratio */ - const STRETCH = 2; + const STRETCH = 0b0010; /** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */ - const FIT = 0; + const FIT = 0b0000; /** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */ - const FILL = 4; + const FILL = 0b0100; /** {@link resize()} fills given area exactly */ - const EXACT = 8; + const EXACT = 0b1000; /** image types */ const JPEG = IMAGETYPE_JPEG, @@ -122,12 +128,12 @@ */ public static function rgb($red, $green, $blue, $transparency = 0) { - return array( + return [ 'red' => max(0, min(255, (int) $red)), 'green' => max(0, min(255, (int) $green)), 'blue' => max(0, min(255, (int) $blue)), 'alpha' => max(0, min(127, (int) $transparency)), - ); + ]; } @@ -145,36 +151,23 @@ throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); } - static $funcs = array( + static $funcs = [ self::JPEG => 'imagecreatefromjpeg', self::PNG => 'imagecreatefrompng', self::GIF => 'imagecreatefromgif', - ); - $info = @getimagesize($file); // @ - files smaller than 12 bytes causes read error - $format = $info[2]; + ]; + $format = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error if (!isset($funcs[$format])) { throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); } - return new static(Callback::invokeSafe($funcs[$format], array($file), function ($message) { + return new static(Callback::invokeSafe($funcs[$format], [$file], function ($message) { throw new ImageException($message); })); } /** - * @deprecated - */ - public static function getFormatFromString($s) - { - trigger_error(__METHOD__ . '() is deprecated; use finfo_buffer() instead.', E_USER_DEPRECATED); - $types = array('image/jpeg' => self::JPEG, 'image/gif' => self::GIF, 'image/png' => self::PNG); - $type = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $s); - return isset($types[$type]) ? $types[$type] : NULL; - } - - - /** * Create a new image from the image stream in the string. * @param string * @param mixed detected image format @@ -188,10 +181,11 @@ } if (func_num_args() > 1) { - $format = @static::getFormatFromString($s); // @ suppress trigger_error + $tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error + $format = in_array($tmp, [self::JPEG, self::PNG, self::GIF], TRUE) ? $tmp : NULL; } - return new static(Callback::invokeSafe('imagecreatefromstring', array($s), function ($message) { + return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function ($message) { throw new ImageException($message); })); } @@ -218,7 +212,7 @@ $image = imagecreatetruecolor($width, $height); if (is_array($color)) { - $color += array('alpha' => 0); + $color += ['alpha' => 0]; $color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']); imagealphablending($image, FALSE); imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color); @@ -309,14 +303,8 @@ $this->image = $newImage; } - if ($width < 0 || $height < 0) { // flip is processed in two steps for better quality - $newImage = static::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource(); - imagecopyresampled( - $newImage, $this->image, - 0, 0, $width < 0 ? $newWidth - 1 : 0, $height < 0 ? $newHeight - 1 : 0, - $newWidth, $newHeight, $width < 0 ? -$newWidth : $newWidth, $height < 0 ? -$newHeight : $newHeight - ); - $this->image = $newImage; + if ($width < 0 || $height < 0) { + imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); } return $this; } @@ -333,15 +321,15 @@ */ public static function calculateSize($srcWidth, $srcHeight, $newWidth, $newHeight, $flags = self::FIT) { - if (substr($newWidth, -1) === '%') { - $newWidth = round($srcWidth / 100 * abs($newWidth)); + if (is_string($newWidth) && substr($newWidth, -1) === '%') { + $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); $percents = TRUE; } else { $newWidth = (int) abs($newWidth); } - if (substr($newHeight, -1) === '%') { - $newHeight = round($srcHeight / 100 * abs($newHeight)); + if (is_string($newHeight) && substr($newHeight, -1) === '%') { + $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); $flags |= empty($percents) ? 0 : self::STRETCH; } else { $newHeight = (int) abs($newHeight); @@ -353,8 +341,8 @@ } if ($flags & self::SHRINK_ONLY) { - $newWidth = round($srcWidth * min(1, $newWidth / $srcWidth)); - $newHeight = round($srcHeight * min(1, $newHeight / $srcHeight)); + $newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth)); + $newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight)); } } else { // proportional @@ -362,7 +350,7 @@ throw new Nette\InvalidArgumentException('At least width or height must be specified.'); } - $scale = array(); + $scale = []; if ($newWidth > 0) { // fit width $scale[] = $newWidth / $srcWidth; } @@ -372,7 +360,7 @@ } if ($flags & self::FILL) { - $scale = array(max($scale)); + $scale = [max($scale)]; } if ($flags & self::SHRINK_ONLY) { @@ -380,11 +368,11 @@ } $scale = min($scale); - $newWidth = round($srcWidth * $scale); - $newHeight = round($srcHeight * $scale); + $newWidth = (int) round($srcWidth * $scale); + $newHeight = (int) round($srcHeight * $scale); } - return array(max((int) $newWidth, 1), max((int) $newHeight, 1)); + return [max($newWidth, 1), max($newHeight, 1)]; } @@ -398,10 +386,15 @@ */ public function crop($left, $top, $width, $height) { - list($left, $top, $width, $height) = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); - $newImage = static::fromBlank($width, $height, self::RGB(0, 0, 0, 127))->getImageResource(); - imagecopy($newImage, $this->image, 0, 0, $left, $top, $width, $height); - $this->image = $newImage; + list($r['x'], $r['y'], $r['width'], $r['height']) + = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); + if (PHP_VERSION_ID > 50611) { // PHP bug #67447 + $this->image = imagecrop($this->image, $r); + } else { + $newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource(); + imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']); + $this->image = $newImage; + } return $this; } @@ -418,17 +411,17 @@ */ public static function calculateCutout($srcWidth, $srcHeight, $left, $top, $newWidth, $newHeight) { - if (substr($newWidth, -1) === '%') { - $newWidth = round($srcWidth / 100 * $newWidth); + if (is_string($newWidth) && substr($newWidth, -1) === '%') { + $newWidth = (int) round($srcWidth / 100 * $newWidth); } - if (substr($newHeight, -1) === '%') { - $newHeight = round($srcHeight / 100 * $newHeight); + if (is_string($newHeight) && substr($newHeight, -1) === '%') { + $newHeight = (int) round($srcHeight / 100 * $newHeight); } - if (substr($left, -1) === '%') { - $left = round(($srcWidth - $newWidth) / 100 * $left); + if (is_string($left) && substr($left, -1) === '%') { + $left = (int) round(($srcWidth - $newWidth) / 100 * $left); } - if (substr($top, -1) === '%') { - $top = round(($srcHeight - $newHeight) / 100 * $top); + if (is_string($top) && substr($top, -1) === '%') { + $top = (int) round(($srcHeight - $newHeight) / 100 * $top); } if ($left < 0) { $newWidth += $left; @@ -438,9 +431,9 @@ $newHeight += $top; $top = 0; } - $newWidth = min((int) $newWidth, $srcWidth - $left); - $newHeight = min((int) $newHeight, $srcHeight - $top); - return array($left, $top, $newWidth, $newHeight); + $newWidth = min($newWidth, $srcWidth - $left); + $newHeight = min($newHeight, $srcHeight - $top); + return [$left, $top, $newWidth, $newHeight]; } @@ -450,11 +443,11 @@ */ public function sharpen() { - imageconvolution($this->image, array( // my magic numbers ;) - array(-1, -1, -1), - array(-1, 24, -1), - array(-1, -1, -1), - ), 16, 0); + imageconvolution($this->image, [ // my magic numbers ;) + [-1, -1, -1], + [-1, 24, -1], + [-1, -1, -1], + ], 16, 0); return $this; } @@ -470,38 +463,48 @@ public function place(Image $image, $left = 0, $top = 0, $opacity = 100) { $opacity = max(0, min(100, (int) $opacity)); - - if (substr($left, -1) === '%') { - $left = round(($this->getWidth() - $image->getWidth()) / 100 * $left); + if ($opacity === 0) { + return $this; } - if (substr($top, -1) === '%') { - $top = round(($this->getHeight() - $image->getHeight()) / 100 * $top); + $width = $image->getWidth(); + $height = $image->getHeight(); + + if (is_string($left) && substr($left, -1) === '%') { + $left = (int) round(($this->getWidth() - $width) / 100 * $left); } - if ($opacity === 100) { - imagecopy( - $this->image, $image->getImageResource(), - $left, $top, 0, 0, $image->getWidth(), $image->getHeight() - ); + if (is_string($top) && substr($top, -1) === '%') { + $top = (int) round(($this->getHeight() - $height) / 100 * $top); + } - } elseif ($opacity != 0) { - $cutting = imagecreatetruecolor($image->getWidth(), $image->getHeight()); - imagecopy( - $cutting, $this->image, - 0, 0, $left, $top, $image->getWidth(), $image->getHeight() - ); - imagecopy( - $cutting, $image->getImageResource(), - 0, 0, 0, 0, $image->getWidth(), $image->getHeight() - ); + $output = $input = $image->image; + if ($opacity < 100) { + for ($i = 0; $i < 128; $i++) { + $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); + } - imagecopymerge( - $this->image, $cutting, - $left, $top, 0, 0, $image->getWidth(), $image->getHeight(), - $opacity - ); + $output = imagecreatetruecolor($width, $height); + imagealphablending($output, FALSE); + if (!$image->isTrueColor()) { + $input = $output; + imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); + imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); + } + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $c = \imagecolorat($input, $x, $y); + $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); + \imagesetpixel($output, $x, $y, $c); + } + } + imagealphablending($output, TRUE); } + + imagecopy( + $this->image, $output, + $left, $top, 0, 0, $width, $height + ); return $this; } @@ -592,7 +595,7 @@ */ public function send($type = self::JPEG, $quality = NULL) { - if (!in_array($type, array(self::JPEG, self::PNG, self::GIF), TRUE)) { + if (!in_array($type, [self::JPEG, self::PNG, self::GIF], TRUE)) { throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); } header('Content-Type: ' . image_type_to_mime_type($type)); @@ -612,7 +615,7 @@ { $function = 'image' . $name; if (!function_exists($function)) { - return parent::__call($name, $args); + ObjectMixin::strictCall(get_class($this), $name); } foreach ($args as $key => $value) { @@ -629,9 +632,7 @@ ); } } - array_unshift($args, $this->image); - - $res = call_user_func_array($function, $args); + $res = $function($this->image, ...$args); return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res; } diff -Nru php-nette-2.3.10/Nette/Utils/Json.php php-nette-2.4-20160731/Nette/Utils/Json.php --- php-nette-2.3.10/Nette/Utils/Json.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Json.php 2016-07-31 17:46:44.000000000 +0000 @@ -15,26 +15,10 @@ */ class Json { - const FORCE_ARRAY = 1; - const PRETTY = 2; + use Nette\StaticClass; - /** @var array */ - private static $messages = array( - JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', - JSON_ERROR_STATE_MISMATCH => 'Syntax error, malformed JSON', - JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', - JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', - 5 /*JSON_ERROR_UTF8*/ => 'Invalid UTF-8 sequence', // exists since 5.3.3, but is returned since 5.3.1 - ); - - - /** - * Static class - cannot be instantiated. - */ - final public function __construct() - { - throw new Nette\StaticClassException; - } + const FORCE_ARRAY = 0b0001; + const PRETTY = 0b0010; /** @@ -45,23 +29,16 @@ */ public static function encode($value, $options = 0) { - $flags = PHP_VERSION_ID >= 50400 ? (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | ($options & self::PRETTY ? JSON_PRETTY_PRINT : 0)) : 0; - - if (PHP_VERSION_ID < 50500) { - $json = Callback::invokeSafe('json_encode', array($value, $flags), function ($message) { // needed to receive 'recursion detected' error - throw new JsonException($message); - }); - } else { - $json = json_encode($value, $flags); - } + $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES + | ($options & self::PRETTY ? JSON_PRETTY_PRINT : 0) + | (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7 + $json = json_encode($value, $flags); if ($error = json_last_error()) { - $message = isset(static::$messages[$error]) ? static::$messages[$error] - : (PHP_VERSION_ID >= 50500 ? json_last_error_msg() : 'Unknown error'); - throw new JsonException($message, $error); + throw new JsonException(json_last_error_msg(), $error); } - $json = str_replace(array("\xe2\x80\xa8", "\xe2\x80\xa9"), array('\u2028', '\u2029'), $json); + $json = str_replace(["\xe2\x80\xa8", "\xe2\x80\xa9"], ['\u2028', '\u2029'], $json); return $json; } @@ -75,23 +52,21 @@ public static function decode($json, $options = 0) { $json = (string) $json; - if (!preg_match('##u', $json)) { - throw new JsonException('Invalid UTF-8 sequence', 5); // workaround for PHP < 5.3.3 & PECL JSON-C + if (defined('JSON_C_VERSION') && !preg_match('##u', $json)) { + throw new JsonException('Invalid UTF-8 sequence', 5); + } elseif ($json === '') { // for PHP < 7 + throw new JsonException('Syntax error'); } $forceArray = (bool) ($options & self::FORCE_ARRAY); - if (!$forceArray && preg_match('#(?<=[^\\\\]")\\\\u0000(?:[^"\\\\]|\\\\.)*+"\s*+:#', $json)) { // workaround for json_decode fatal error when object key starts with \u0000 - throw new JsonException(static::$messages[JSON_ERROR_CTRL_CHAR]); + if (PHP_VERSION_ID < 70000 && !$forceArray && preg_match('#(?<=[^\\\\]")\\\\u0000(?:[^"\\\\]|\\\\.)*+"\s*+:#', $json)) { + throw new JsonException('The decoded property name is invalid'); // workaround for json_decode fatal error when object key starts with \u0000 } - $args = array($json, $forceArray, 512); - if (PHP_VERSION_ID >= 50400 && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { // not implemented in PECL JSON-C 1.3.2 for 64bit systems - $args[] = JSON_BIGINT_AS_STRING; - } - $value = call_user_func_array('json_decode', $args); + $flags = !defined('JSON_C_VERSION') || PHP_INT_SIZE === 4 ? JSON_BIGINT_AS_STRING : 0; // not implemented in PECL JSON-C 1.3.2 for 64bit systems - if ($value === NULL && $json !== '' && strcasecmp(trim($json, " \t\n\r"), 'null') !== 0) { // '' is not clearing json_last_error - $error = json_last_error(); - throw new JsonException(isset(static::$messages[$error]) ? static::$messages[$error] : 'Unknown error', $error); + $value = json_decode($json, $forceArray, 512, $flags); + if ($error = json_last_error()) { + throw new JsonException(json_last_error_msg(), $error); } return $value; } diff -Nru php-nette-2.3.10/Nette/Utils/ObjectMixin.php php-nette-2.4-20160731/Nette/Utils/ObjectMixin.php --- php-nette-2.3.10/Nette/Utils/ObjectMixin.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/ObjectMixin.php 2016-07-31 17:46:44.000000000 +0000 @@ -16,25 +16,77 @@ */ class ObjectMixin { - /** @var array (name => 0 | bool | array) used by getMethods() */ - private static $methods; + use Nette\StaticClass; - /** @var array (name => 'event' | TRUE) used by hasProperty() */ - private static $props; + /** @var array [name => [type => callback]] used by extension methods */ + private static $extMethods = []; - /** @var array (name => array(type => callback)) used by get|setExtensionMethod() */ - private static $extMethods; + + /********************* strictness ****************d*g**/ + + + /** + * @throws MemberAccessException + */ + public static function strictGet($class, $name) + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + ), $name); + throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @throws MemberAccessException + */ + public static function strictSet($class, $name) + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + ), $name); + throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } /** - * Static class - cannot be instantiated. + * @throws MemberAccessException */ - final public function __construct() + public static function strictCall($class, $method, $additionalMethods = []) { - throw new Nette\StaticClassException; + $hint = self::getSuggestion(array_merge( + get_class_methods($class), + self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'), + $additionalMethods + ), $method); + + if (method_exists($class, $method)) { // called parent::$method() + $class = 'parent'; + } + throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + + + /** + * @throws MemberAccessException + */ + public static function strictStaticCall($class, $method) + { + $hint = self::getSuggestion( + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }), + $method + ); + throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); } + /********************* Nette\Object ****************d*g**/ + + /** * __call() implementation. * @param object @@ -51,9 +103,6 @@ if ($name === '') { throw new MemberAccessException("Call to class '$class' method without name."); - } elseif ($isProp && $_this->$name instanceof \Closure) { // closure in property - return call_user_func_array($_this->$name, $args); - } elseif ($isProp === 'event') { // calling event handlers if (is_array($_this->$name) || $_this->$name instanceof \Traversable) { foreach ($_this->$name as $handler) { @@ -63,6 +112,9 @@ throw new Nette\UnexpectedValueException("Property $class::$$name must be array or NULL, " . gettype($_this->$name) . ' given.'); } + } elseif ($isProp && $_this->$name instanceof \Closure) { // closure in property + return call_user_func_array($_this->$name, $args); + } elseif (($methods = & self::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) { // magic @methods list($op, $rp, $type) = $methods[$name]; if (count($args) !== ($op === 'get' ? 0 : 1)) { @@ -84,20 +136,10 @@ return $_this; } elseif ($cb = self::getExtensionMethod($class, $name)) { // extension methods - array_unshift($args, $_this); - return Callback::invokeArgs($cb, $args); + return Callback::invoke($cb, $_this, ...$args); } else { - $hint = self::getSuggestion(array_merge( - get_class_methods($class), - self::parseFullDoc($class, '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'), - array_keys(self::getExtensionMethods($class)) - ), $name); - - if (method_exists($class, $name)) { // called parent::$name() - $class = 'parent'; - } - throw new MemberAccessException("Call to undefined method $class::$name()" . ($hint ? ", did you mean $hint()?" : '.')); + self::strictCall($class, $name, array_keys(self::getExtensionMethods($class))); } } @@ -112,11 +154,7 @@ */ public static function callStatic($class, $method, $args) { - $hint = self::getSuggestion(array_filter( - get_class_methods($class), - function ($m) use ($class) { $rm = new \ReflectionMethod($class, $m); return $rm->isStatic(); } - ), $method); - throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + self::strictStaticCall($class, $method); } @@ -138,8 +176,7 @@ } elseif (isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) { // property getter if ($methods[$m] === 0) { - $rm = new \ReflectionMethod($class, $m); - $methods[$m] = $rm->returnsReference(); + $methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference(); } if ($methods[$m] === TRUE) { return $_this->$m(); @@ -149,28 +186,17 @@ } } elseif (isset($methods[$name])) { // public method as closure getter - if (preg_match('#^(is|get|has)([A-Z]|$)#', $name) && ($rm = new \ReflectionMethod($class, $name)) && !$rm->getNumberOfRequiredParameters()) { - $source = ''; - foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) { - if (isset($item['file']) && dirname($item['file']) !== __DIR__) { - $source = " in $item[file]:$item[line]"; - break; - } - } - trigger_error("Did you forgot parentheses after $name$source?", E_USER_WARNING); + if (preg_match('#^(is|get|has)([A-Z]|$)#', $name) && !(new \ReflectionMethod($class, $name))->getNumberOfRequiredParameters()) { + trigger_error("Did you forget parentheses after $name" . self::getSource() . '?', E_USER_WARNING); } $val = Callback::closure($_this, $name); return $val; - } elseif (isset($methods['set' . $uname])) { // strict class + } elseif (isset($methods['set' . $uname])) { // property getter throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); - } else { // strict class - $hint = self::getSuggestion(array_merge( - array_keys(get_class_vars($class)), - self::parseFullDoc($class, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') - ), $name); - throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } else { + self::strictGet($class, $name); } } @@ -198,15 +224,11 @@ } elseif (isset($methods[$m = 'set' . $uname])) { // property setter $_this->$m($value); - } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // strict class + } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // property setter throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); - } else { // strict class - $hint = self::getSuggestion(array_merge( - array_keys(get_class_vars($class)), - self::parseFullDoc($class, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') - ), $name); - throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } else { + self::strictSet($class, $name); } } @@ -221,7 +243,7 @@ public static function remove($_this, $name) { $class = get_class($_this); - if (!self::hasProperty($class, $name)) { // strict class + if (!self::hasProperty($class, $name)) { throw new MemberAccessException("Cannot unset the property $class::\$$name."); } } @@ -241,43 +263,60 @@ } + /********************* magic @properties ****************d*g**/ + + /** - * Checks if the public non-static property exists. - * @return mixed + * Returns array of magic properties defined by annotation @property. + * @return array of [name => bit mask] */ - private static function hasProperty($class, $name) + public static function getMagicProperties($class) { - $prop = & self::$props[$class][$name]; - if ($prop === NULL) { - $prop = FALSE; - try { - $rp = new \ReflectionProperty($class, $name); - if ($rp->isPublic() && !$rp->isStatic()) { - $prop = $name >= 'onA' && $name < 'on_' ? 'event' : TRUE; - } - } catch (\ReflectionException $e) { + static $cache; + $props = & $cache[$class]; + if ($props !== NULL) { + return $props; + } + + $rc = new \ReflectionClass($class); + preg_match_all( + '~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + (string) $rc->getDocComment(), $matches, PREG_SET_ORDER + ); + + $props = []; + foreach ($matches as list(, $type, $name)) { + $uname = ucfirst($name); + $write = $type !== '-read' + && $rc->hasMethod($nm = 'set' . $uname) + && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic(); + $read = $type !== '-write' + && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) + && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic(); + + if ($read || $write) { + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3; } } - return $prop; + + if ($parent = get_parent_class($class)) { + $props += self::getMagicProperties($parent); + } + return $props; } - /** - * Returns array of public (static, non-static and magic) methods. - * @return array - */ - private static function & getMethods($class) + /** @internal */ + public static function getMagicProperty($class, $name) { - if (!isset(self::$methods[$class])) { - self::$methods[$class] = array_fill_keys(get_class_methods($class), 0) + self::getMagicMethods($class); - if ($parent = get_parent_class($class)) { - self::$methods[$class] += self::getMethods($parent); - } - } - return self::$methods[$class]; + $props = self::getMagicProperties($class); + return isset($props[$name]) ? $props[$name] : NULL; } + /********************* magic @methods ****************d*g**/ + + /** * Returns array of magic methods defined by annotation @method. * @return array @@ -288,13 +327,15 @@ preg_match_all('~^ [ \t*]* @method [ \t]+ (?: [^\s(]+ [ \t]+ )? - (set|get|is|add) ([A-Z]\w*) [ \t]* - (?: \( [ \t]* ([^)$\s]+) )? - ()~mx', $rc->getDocComment(), $matches, PREG_SET_ORDER); - - $methods = array(); - foreach ($matches as $m) { - list(, $op, $prop, $type) = $m; + (set|get|is|add) ([A-Z]\w*) + (?: ([ \t]* \() [ \t]* ([^)$\s]*) )? + ()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER); + + $methods = []; + foreach ($matches as list(, $op, $prop, $bracket, $type)) { + if ($bracket !== '(') { + trigger_error("Bracket must be immediately after @method $op$prop() in class $class.", E_USER_WARNING); + } $name = $op . $prop; $prop = strtolower($prop[0]) . substr($prop, 1) . ($op === 'add' ? 's' : ''); if ($rc->hasProperty($prop) && ($rp = $rc->getProperty($prop)) && !$rp->isStatic()) { @@ -302,13 +343,13 @@ if ($op === 'get' || $op === 'is') { $type = NULL; $op = 'get'; - } elseif (!$type && preg_match('#@var[ \t]+(\S+)' . ($op === 'add' ? '\[\]#' : '#'), $rp->getDocComment(), $m)) { + } elseif (!$type && preg_match('#@var[ \t]+(\S+)' . ($op === 'add' ? '\[\]#' : '#'), (string) $rp->getDocComment(), $m)) { $type = $m[1]; } - if ($rc->inNamespace() && preg_match('#^[A-Z]\w+(\[|\||\z)#', $type)) { + if ($rc->inNamespace() && preg_match('#^[A-Z]\w+(\[|\||\z)#', (string) $type)) { $type = $rc->getNamespaceName() . '\\' . $type; } - $methods[$name] = array($op, $rp, $type); + $methods[$name] = [$op, $rp, $type]; } } return $methods; @@ -344,7 +385,7 @@ return FALSE; } $type = substr($type, 0, -2); - $res = array(); + $res = []; foreach ($val as $k => $v) { if (!self::checkType($v, $type)) { return FALSE; @@ -382,6 +423,9 @@ } + /********************* extension methods ****************d*g**/ + + /** * Adds a method to class. * @param string @@ -411,7 +455,7 @@ return $cache; } - foreach (array($class) + class_parents($class) + class_implements($class) as $cl) { + foreach ([$class] + class_parents($class) + class_implements($class) as $cl) { if (isset($list[$cl])) { return $cache = $list[$cl]; } @@ -427,7 +471,7 @@ */ public static function getExtensionMethods($class) { - $res = array(); + $res = []; foreach (array_keys(self::$extMethods) as $name) { if ($cb = self::getExtensionMethod($class, $name)) { $res[$name] = $cb; @@ -437,17 +481,21 @@ } + /********************* utilities ****************d*g**/ + + /** * Finds the best suggestion (for 8-bit encoding). * @return string|NULL * @internal */ - public static function getSuggestion(array $items, $value) + public static function getSuggestion(array $possibilities, $value) { $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value); $best = NULL; $min = (strlen($value) / 4 + 1) * 10 + .1; - foreach (array_unique($items) as $item) { + foreach (array_unique($possibilities, SORT_REGULAR) as $item) { + $item = $item instanceof \Reflector ? $item->getName() : $item; if ($item !== $value && ( ($len = levenshtein($item, $value, 10, 11, 10)) < $min || ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min @@ -460,13 +508,64 @@ } - private static function parseFullDoc($class, $pattern) + private static function parseFullDoc(\ReflectionClass $rc, $pattern) { - $rc = new \ReflectionClass($class); do { $doc[] = $rc->getDocComment(); } while ($rc = $rc->getParentClass()); - return preg_match_all($pattern, implode($doc), $m) ? $m[1] : array(); + return preg_match_all($pattern, implode($doc), $m) ? $m[1] : []; + } + + + /** + * Checks if the public non-static property exists. + * @return bool|'event' + * @internal + */ + public static function hasProperty($class, $name) + { + static $cache; + $prop = & $cache[$class][$name]; + if ($prop === NULL) { + $prop = FALSE; + try { + $rp = new \ReflectionProperty($class, $name); + if ($rp->isPublic() && !$rp->isStatic()) { + $prop = $name >= 'onA' && $name < 'on_' ? 'event' : TRUE; + } + } catch (\ReflectionException $e) { + } + } + return $prop; + } + + + /** + * Returns array of public (static, non-static and magic) methods. + * @return array + * @internal + */ + public static function & getMethods($class) + { + static $cache; + if (!isset($cache[$class])) { + $cache[$class] = array_fill_keys(get_class_methods($class), 0) + self::getMagicMethods($class); + if ($parent = get_parent_class($class)) { + $cache[$class] += self::getMethods($parent); + } + } + return $cache[$class]; + } + + + /** @internal */ + public static function getSource() + { + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) { + if (isset($item['file']) && dirname($item['file']) !== __DIR__) { + return " in $item[file]:$item[line]"; + } + } } } diff -Nru php-nette-2.3.10/Nette/Utils/Object.php php-nette-2.4-20160731/Nette/Utils/Object.php --- php-nette-2.3.10/Nette/Utils/Object.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Object.php 2016-07-31 17:46:44.000000000 +0000 @@ -8,7 +8,6 @@ namespace Nette; use Nette; -use Nette\Utils\ObjectMixin; /** @@ -58,7 +57,7 @@ */ public static function getReflection() { - $class = class_exists('Nette\Reflection\ClassType') ? 'Nette\Reflection\ClassType' : 'ReflectionClass'; + $class = class_exists(Nette\Reflection\ClassType::class) ? Nette\Reflection\ClassType::class : 'ReflectionClass'; return new $class(get_called_class()); } @@ -72,7 +71,7 @@ */ public function __call($name, $args) { - return ObjectMixin::call($this, $name, $args); + return Nette\Utils\ObjectMixin::call($this, $name, $args); } @@ -85,7 +84,7 @@ */ public static function __callStatic($name, $args) { - return ObjectMixin::callStatic(get_called_class(), $name, $args); + return Nette\Utils\ObjectMixin::callStatic(get_called_class(), $name, $args); } @@ -101,13 +100,12 @@ $class = get_called_class(); } else { list($class, $name) = explode('::', $name); - $rc = new \ReflectionClass($class); - $class = $rc->getName(); + $class = (new \ReflectionClass($class))->getName(); } if ($callback === NULL) { - return ObjectMixin::getExtensionMethod($class, $name); + return Nette\Utils\ObjectMixin::getExtensionMethod($class, $name); } else { - ObjectMixin::setExtensionMethod($class, $name, $callback); + Nette\Utils\ObjectMixin::setExtensionMethod($class, $name, $callback); } } @@ -120,7 +118,7 @@ */ public function &__get($name) { - return ObjectMixin::get($this, $name); + return Nette\Utils\ObjectMixin::get($this, $name); } @@ -133,7 +131,7 @@ */ public function __set($name, $value) { - ObjectMixin::set($this, $name, $value); + Nette\Utils\ObjectMixin::set($this, $name, $value); } @@ -144,7 +142,7 @@ */ public function __isset($name) { - return ObjectMixin::has($this, $name); + return Nette\Utils\ObjectMixin::has($this, $name); } @@ -156,7 +154,7 @@ */ public function __unset($name) { - ObjectMixin::remove($this, $name); + Nette\Utils\ObjectMixin::remove($this, $name); } } diff -Nru php-nette-2.3.10/Nette/Utils/Paginator.php php-nette-2.4-20160731/Nette/Utils/Paginator.php --- php-nette-2.3.10/Nette/Utils/Paginator.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Paginator.php 2016-07-31 17:46:44.000000000 +0000 @@ -26,8 +26,10 @@ * @property-read int|NULL $countdownOffset * @property-read int|NULL $length */ -class Paginator extends Nette\Object +class Paginator { + use Nette\SmartObject; + /** @var int */ private $base = 1; diff -Nru php-nette-2.3.10/Nette/Utils/Random.php php-nette-2.4-20160731/Nette/Utils/Random.php --- php-nette-2.3.10/Nette/Utils/Random.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Random.php 2016-07-31 17:46:44.000000000 +0000 @@ -7,12 +7,15 @@ namespace Nette\Utils; +use Nette; + /** * Secure random string generator. */ class Random { + use Nette\StaticClass; /** * Generate random string. @@ -22,15 +25,15 @@ */ public static function generate($length = 10, $charlist = '0-9a-z') { - $charlist = count_chars(preg_replace_callback('#.-.#', function ($m) { + $charlist = count_chars(preg_replace_callback('#.-.#', function (array $m) { return implode('', range($m[0][0], $m[0][2])); }, $charlist), 3); $chLen = strlen($charlist); if ($length < 1) { - return ''; // mcrypt_create_iv does not support zero length + throw new Nette\InvalidArgumentException('Length must be greater than zero.'); } elseif ($chLen < 2) { - return str_repeat($charlist, $length); // random_int does not support empty interval + throw new Nette\InvalidArgumentException('Character list must contain as least two chars.'); } $res = ''; @@ -41,21 +44,18 @@ return $res; } - $windows = defined('PHP_WINDOWS_VERSION_BUILD'); $bytes = ''; - if (function_exists('openssl_random_pseudo_bytes') - && (PHP_VERSION_ID >= 50400 || !defined('PHP_WINDOWS_VERSION_BUILD')) // slow in PHP 5.3 & Windows - ) { - $bytes = openssl_random_pseudo_bytes($length, $secure); + if (function_exists('openssl_random_pseudo_bytes')) { + $bytes = (string) openssl_random_pseudo_bytes($length, $secure); if (!$secure) { $bytes = ''; } } - if (strlen($bytes) < $length && function_exists('mcrypt_create_iv') && (PHP_VERSION_ID >= 50307 || !$windows)) { // PHP bug #52523 - $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if (strlen($bytes) < $length && function_exists('mcrypt_create_iv')) { + $bytes = (string) mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); } - if (strlen($bytes) < $length && !$windows && @is_readable('/dev/urandom')) { - $bytes = file_get_contents('/dev/urandom', FALSE, NULL, -1, $length); + if (strlen($bytes) < $length && !defined('PHP_WINDOWS_VERSION_BUILD') && is_readable('/dev/urandom')) { + $bytes = (string) file_get_contents('/dev/urandom', FALSE, NULL, -1, $length); } if (strlen($bytes) < $length) { $rand3 = md5(serialize($_SERVER), TRUE); diff -Nru php-nette-2.3.10/Nette/Utils/SmartObject.php php-nette-2.4-20160731/Nette/Utils/SmartObject.php --- php-nette-2.3.10/Nette/Utils/SmartObject.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/SmartObject.php 2016-07-31 17:46:44.000000000 +0000 @@ -0,0 +1,231 @@ +$name) || $this->$name instanceof \Traversable) { + foreach ($this->$name as $handler) { + Callback::invokeArgs($handler, $args); + } + } elseif ($this->$name !== NULL) { + throw new UnexpectedValueException("Property $class::$$name must be array or NULL, " . gettype($this->$name) . ' given.'); + } + + } elseif ($isProp && $this->$name instanceof \Closure) { // closure in property + trigger_error("Invoking closure in property via \$obj->$name() is deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED); + return call_user_func_array($this->$name, $args); + + } elseif (($methods = & ObjectMixin::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) { // magic @methods + trigger_error("Magic methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED); + list($op, $rp, $type) = $methods[$name]; + if (count($args) !== ($op === 'get' ? 0 : 1)) { + throw new InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.'); + + } elseif ($type && $args && !ObjectMixin::checkType($args[0], $type)) { + throw new InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.'); + } + + if ($op === 'get') { + return $rp->getValue($this); + } elseif ($op === 'set') { + $rp->setValue($this, $args[0]); + } elseif ($op === 'add') { + $val = $rp->getValue($this); + $val[] = $args[0]; + $rp->setValue($this, $val); + } + return $this; + + } elseif ($cb = ObjectMixin::getExtensionMethod($class, $name)) { // extension methods + trigger_error("Extension methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED); + return Callback::invoke($cb, $this, ...$args); + + } else { + ObjectMixin::strictCall($class, $name); + } + } + + + /** + * @return void + * @throws MemberAccessException + */ + public static function __callStatic($name, $args) + { + ObjectMixin::strictStaticCall(get_called_class(), $name); + } + + + /** + * @return mixed property value + * @throws MemberAccessException if the property is not defined. + */ + public function & __get($name) + { + $class = get_class($this); + $uname = ucfirst($name); + + if ($prop = ObjectMixin::getMagicProperty($class, $name)) { // property getter + if (!($prop & 0b0001)) { + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + } + $m = ($prop & 0b0010 ? 'get' : 'is') . $uname; + if ($prop & 0b0100) { // return by reference + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + + } elseif ($name === '') { + throw new MemberAccessException("Cannot read a class '$class' property without name."); + + } elseif (($methods = & ObjectMixin::getMethods($class)) && isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) { // old property getter + trigger_error("Missing annotation @property for $class::\$$name used" . ObjectMixin::getSource(), E_USER_DEPRECATED); + if ($methods[$m] === 0) { + $methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference(); + } + if ($methods[$m] === TRUE) { + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + + } elseif (isset($methods[$name])) { // public method as closure getter + trigger_error("Accessing methods as properties via \$obj->$name is deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED); + $val = Callback::closure($this, $name); + return $val; + + } elseif (isset($methods['set' . $uname])) { // property getter + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + + } else { + ObjectMixin::strictGet($class, $name); + } + } + + + /** + * @return void + * @throws MemberAccessException if the property is not defined or is read-only + */ + public function __set($name, $value) + { + $class = get_class($this); + $uname = ucfirst($name); + + if (ObjectMixin::hasProperty($class, $name)) { // unsetted property + $this->$name = $value; + + } elseif ($prop = ObjectMixin::getMagicProperty($class, $name)) { // property setter + if (!($prop & 0b1000)) { + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + } + $this->{'set' . $name}($value); + + } elseif ($name === '') { + throw new MemberAccessException("Cannot write to a class '$class' property without name."); + + } elseif (($methods = & ObjectMixin::getMethods($class)) && isset($methods[$m = 'set' . $uname])) { // old property setter + trigger_error("Missing annotation @property for $class::\$$name used" . ObjectMixin::getSource(), E_USER_DEPRECATED); + $this->$m($value); + + } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // property setter + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + + } else { + ObjectMixin::strictSet($class, $name); + } + } + + + /** + * @return void + * @throws MemberAccessException + */ + public function __unset($name) + { + $class = get_class($this); + if (!ObjectMixin::hasProperty($class, $name)) { + throw new MemberAccessException("Cannot unset the property $class::\$$name."); + } + } + + + /** + * @return bool + */ + public function __isset($name) + { + $uname = ucfirst($name); + return ObjectMixin::getMagicProperty(get_class($this), $name) + || ($name !== '' && ($methods = ObjectMixin::getMethods(get_class($this))) && (isset($methods['get' . $uname]) || isset($methods['is' . $uname]))); + } + + + /** + * @return Reflection\ClassType|\ReflectionClass + * @deprecated + */ + public static function getReflection() + { + trigger_error(get_called_class() . '::getReflection() is deprecated' . ObjectMixin::getSource(), E_USER_DEPRECATED); + $class = class_exists(Reflection\ClassType::class) ? Reflection\ClassType::class : \ReflectionClass::class; + return new $class(get_called_class()); + } + + + /** + * @return mixed + * @deprecated use Nette\Utils\ObjectMixin::setExtensionMethod() + */ + public static function extensionMethod($name, $callback = NULL) + { + if (strpos($name, '::') === FALSE) { + $class = get_called_class(); + } else { + list($class, $name) = explode('::', $name); + $class = (new \ReflectionClass($class))->getName(); + } + trigger_error("Extension methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED); + if ($callback === NULL) { + return ObjectMixin::getExtensionMethod($class, $name); + } else { + ObjectMixin::setExtensionMethod($class, $name, $callback); + } + } + +} diff -Nru php-nette-2.3.10/Nette/Utils/StaticClass.php php-nette-2.4-20160731/Nette/Utils/StaticClass.php --- php-nette-2.3.10/Nette/Utils/StaticClass.php 1970-01-01 00:00:00.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/StaticClass.php 2016-07-31 17:46:44.000000000 +0000 @@ -0,0 +1,35 @@ +transliterate($s); } if (ICONV_IMPL === 'glibc') { $s = str_replace( - array("\xC2\xBB", "\xC2\xAB", "\xE2\x80\xA6", "\xE2\x84\xA2", "\xC2\xA9", "\xC2\xAE"), - array('>>', '<<', '...', 'TM', '(c)', '(R)'), $s + ["\xC2\xBB", "\xC2\xAB", "\xE2\x80\xA6", "\xE2\x84\xA2", "\xC2\xA9", "\xC2\xAE"], + ['>>', '<<', '...', 'TM', '(c)', '(R)'], $s ); - $s = @iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); // intentionally @ + $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); $s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e" . "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3" . "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8" @@ -197,9 +182,9 @@ 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'); $s = preg_replace('#[^\x00-\x7F]++#', '', $s); } else { - $s = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); // intentionally @ + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); } - $s = str_replace(array('`', "'", '"', '^', '~', '?'), '', $s); + $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); return strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); } @@ -217,7 +202,7 @@ if ($lower) { $s = strtolower($s); } - $s = preg_replace('#[^a-z0-9' . preg_quote($charlist, '#') . ']+#i', '-', $s); + $s = preg_replace('#[^a-z0-9' . ($charlist !== NULL ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s); $s = trim($s, '-'); return $s; } @@ -344,10 +329,10 @@ * @param string|array * @return string */ - public static function findPrefix($strings) + public static function findPrefix(...$strings) { - if (!is_array($strings)) { - $strings = func_get_args(); + if (is_array($strings[0])) { + $strings = $strings[0]; } $first = array_shift($strings); for ($i = 0; $i < strlen($first); $i++) { @@ -400,7 +385,7 @@ { $length = max(0, $length - self::length($s)); $padLen = self::length($pad); - return str_repeat($pad, $length / $padLen) . self::substring($pad, 0, $length % $padLen) . $s; + return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; } @@ -415,7 +400,7 @@ { $length = max(0, $length - self::length($s)); $padLen = self::length($pad); - return $s . str_repeat($pad, $length / $padLen) . self::substring($pad, 0, $length % $padLen); + return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); } @@ -426,7 +411,7 @@ */ public static function reverse($s) { - return @iconv('UTF-32LE', 'UTF-8', strrev(@iconv('UTF-8', 'UTF-32BE', $s))); + return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); } @@ -436,6 +421,7 @@ */ public static function random($length = 10, $charlist = '0-9a-z') { + trigger_error(__METHOD__ . '() is deprecated, use Nette\Utils\Random::generate()', E_USER_DEPRECATED); return Random::generate($length, $charlist); } @@ -474,6 +460,22 @@ /** * Returns position of $nth occurence of $needle in $haystack. + * @param string + * @param string + * @param int negative value means searching from the end + * @return int|FALSE offset in characters or FALSE if the needle was not found + */ + public static function indexOf($haystack, $needle, $nth = 1) + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === FALSE + ? FALSE + : self::length(substr($haystack, 0, $pos)); + } + + + /** + * Returns position of $nth occurence of $needle in $haystack. * @return int|FALSE offset in bytes or FALSE if the needle was not found */ private static function pos($haystack, $needle, $nth = 1) @@ -511,7 +513,7 @@ */ public static function split($subject, $pattern, $flags = 0) { - return self::pcre('preg_split', array($pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE)); + return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]); } @@ -528,7 +530,7 @@ if ($offset > strlen($subject)) { return NULL; } - return self::pcre('preg_match', array($pattern, $subject, & $m, $flags, $offset)) + return self::pcre('preg_match', [$pattern, $subject, & $m, $flags, $offset]) ? $m : NULL; } @@ -545,13 +547,13 @@ public static function matchAll($subject, $pattern, $flags = 0, $offset = 0) { if ($offset > strlen($subject)) { - return array(); + return []; } - self::pcre('preg_match_all', array( + self::pcre('preg_match_all', [ $pattern, $subject, & $m, ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), $offset, - )); + ]); return $m; } @@ -568,41 +570,42 @@ { if (is_object($replacement) || is_array($replacement)) { if ($replacement instanceof Nette\Callback) { + trigger_error('Nette\Callback is deprecated, use PHP callback.', E_USER_DEPRECATED); $replacement = $replacement->getNative(); } if (!is_callable($replacement, FALSE, $textual)) { throw new Nette\InvalidStateException("Callback '$textual' is not callable."); } - return self::pcre('preg_replace_callback', array($pattern, $replacement, $subject, $limit)); + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]); } elseif ($replacement === NULL && is_array($pattern)) { $replacement = array_values($pattern); $pattern = array_keys($pattern); } - return self::pcre('preg_replace', array($pattern, $replacement, $subject, $limit)); + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); } /** @internal */ public static function pcre($func, $args) { - static $messages = array( + static $messages = [ PREG_INTERNAL_ERROR => 'Internal error', PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted', PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted', PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data', PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', 6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR - ); + ]; $res = Callback::invokeSafe($func, $args, function ($message) use ($args) { // compile-time error, not detectable by preg_last_error throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); }); if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars - && ($res === NULL || !in_array($func, array('preg_filter', 'preg_replace_callback', 'preg_replace'))) + && ($res === NULL || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'])) ) { throw new RegexpException((isset($messages[$code]) ? $messages[$code] : 'Unknown error') . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); diff -Nru php-nette-2.3.10/Nette/Utils/Validators.php php-nette-2.4-20160731/Nette/Utils/Validators.php --- php-nette-2.3.10/Nette/Utils/Validators.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Validators.php 2016-07-31 17:46:44.000000000 +0000 @@ -13,32 +13,34 @@ /** * Validation utilities. */ -class Validators extends Nette\Object +class Validators { - protected static $validators = array( + use Nette\StaticClass; + + protected static $validators = [ 'bool' => 'is_bool', 'boolean' => 'is_bool', 'int' => 'is_int', 'integer' => 'is_int', 'float' => 'is_float', 'number' => NULL, // is_int || is_float, - 'numeric' => array(__CLASS__, 'isNumeric'), - 'numericint' => array(__CLASS__, 'isNumericInt'), + 'numeric' => [__CLASS__, 'isNumeric'], + 'numericint' => [__CLASS__, 'isNumericInt'], 'string' => 'is_string', - 'unicode' => array(__CLASS__, 'isUnicode'), + 'unicode' => [__CLASS__, 'isUnicode'], 'array' => 'is_array', - 'list' => array('Nette\Utils\Arrays', 'isList'), + 'list' => [Arrays::class, 'isList'], 'object' => 'is_object', 'resource' => 'is_resource', 'scalar' => 'is_scalar', - 'callable' => array(__CLASS__, 'isCallable'), + 'callable' => [__CLASS__, 'isCallable'], 'null' => 'is_null', - 'email' => array(__CLASS__, 'isEmail'), - 'url' => array(__CLASS__, 'isUrl'), - 'uri' => array(__CLASS__, 'isUri'), - 'none' => array(__CLASS__, 'isNone'), - 'type' => array(__CLASS__, 'isType'), - 'identifier' => array(__CLASS__, 'isPhpIdentifier'), + 'email' => [__CLASS__, 'isEmail'], + 'url' => [__CLASS__, 'isUrl'], + 'uri' => [__CLASS__, 'isUri'], + 'none' => [__CLASS__, 'isNone'], + 'type' => [__CLASS__, 'isType'], + 'identifier' => [__CLASS__, 'isPhpIdentifier'], 'pattern' => NULL, 'alnum' => 'ctype_alnum', 'alpha' => 'ctype_alpha', @@ -47,11 +49,11 @@ 'upper' => 'ctype_upper', 'space' => 'ctype_space', 'xdigit' => 'ctype_xdigit', - ); + ]; - protected static $counters = array( + protected static $counters = [ 'string' => 'strlen', - 'unicode' => array('Nette\Utils\Strings', 'length'), + 'unicode' => [Strings::class, 'length'], 'array' => 'count', 'list' => 'count', 'alnum' => 'strlen', @@ -61,7 +63,7 @@ 'space' => 'strlen', 'upper' => 'strlen', 'xdigit' => 'strlen', - ); + ]; /** @@ -74,7 +76,7 @@ public static function assert($value, $expected, $label = 'variable') { if (!static::is($value, $expected)) { - $expected = str_replace(array('|', ':'), array(' or ', ' in range '), $expected); + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); if (is_array($value)) { $type = 'array(' . count($value) . ')'; } elseif (is_object($value)) { @@ -287,7 +289,7 @@ */ public static function isType($type) { - return class_exists($type) || interface_exists($type) || (PHP_VERSION_ID >= 50400 && trait_exists($type)); + return class_exists($type) || interface_exists($type) || trait_exists($type); } diff -Nru php-nette-2.3.10/readme.txt php-nette-2.4-20160731/readme.txt --- php-nette-2.3.10/readme.txt 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/readme.txt 2016-07-31 17:46:18.000000000 +0000 @@ -43,8 +43,9 @@ The best way to install Nette Framework is to download the latest package from https://nette.org/download or use [Composer](https://doc.nette.org/composer). -All Nette components are 100% compatible with PHP 7. Minimal required version is PHP 5.3.1. Please -run [Requirements Checker](https://doc.nette.org/requirements) to obtain more detailed information. +All Nette components are 100% compatible with PHP 7. Minimal required version of +PHP is 5.6.0 for Nette Framework 2.4-dev and PHP 5.3.1 for stable Nette Framework 2.3. +Please run [Requirements Checker](https://doc.nette.org/requirements) to obtain more detailed information. Getting started diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/checker.phtml php-nette-2.4-20160731/tools/Requirements-Checker/assets/checker.phtml --- php-nette-2.3.10/tools/Requirements-Checker/assets/checker.phtml 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/checker.phtml 1970-01-01 00:00:00.000000000 +0000 @@ -1,218 +0,0 @@ - - - - - - - - - -
\n"; } diff -Nru php-nette-2.3.10/Nette/Utils/ArrayList.php php-nette-2.4-20160731/Nette/Utils/ArrayList.php --- php-nette-2.3.10/Nette/Utils/ArrayList.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/ArrayList.php 2016-07-31 17:46:44.000000000 +0000 @@ -13,9 +13,11 @@ /** * Provides the base class for a generic list (items can be accessed by index). */ -class ArrayList extends Nette\Object implements \ArrayAccess, \Countable, \IteratorAggregate +class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate { - private $list = array(); + use Nette\SmartObject; + + private $list = []; /** @@ -99,4 +101,17 @@ array_splice($this->list, (int) $index, 1); } + + /** + * Prepends a item. + * @param mixed + * @return void + */ + public function prepend($value) + { + $first = array_slice($this->list, 0, 1); + $this->offsetSet(0, $value); + array_splice($this->list, 1, 0, $first); + } + } diff -Nru php-nette-2.3.10/Nette/Utils/Arrays.php php-nette-2.4-20160731/Nette/Utils/Arrays.php --- php-nette-2.3.10/Nette/Utils/Arrays.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Arrays.php 2016-07-31 17:46:44.000000000 +0000 @@ -15,15 +15,7 @@ */ class Arrays { - - /** - * Static class - cannot be instantiated. - */ - final public function __construct() - { - throw new Nette\StaticClassException; - } - + use Nette\StaticClass; /** * Returns item from array or $default if item is not set. @@ -35,7 +27,7 @@ */ public static function get(array $arr, $key, $default = NULL) { - foreach (is_array($key) ? $key : array($key) as $k) { + foreach (is_array($key) ? $key : [$key] as $k) { if (is_array($arr) && array_key_exists($k, $arr)) { $arr = $arr[$k]; } else { @@ -56,9 +48,9 @@ * @return mixed * @throws Nette\InvalidArgumentException if traversed item is not an array */ - public static function & getRef(& $arr, $key) + public static function & getRef(array & $arr, $key) { - foreach (is_array($key) ? $key : array($key) as $k) { + foreach (is_array($key) ? $key : [$key] as $k) { if (is_array($arr) || $arr === NULL) { $arr = & $arr[$k]; } else { @@ -73,7 +65,7 @@ * Recursively appends elements of remaining keys from the second array to the first. * @return array */ - public static function mergeTree($arr1, $arr2) + public static function mergeTree(array $arr1, array $arr2) { $res = $arr1 + $arr2; foreach (array_intersect_key($arr1, $arr2) as $k => $v) { @@ -89,9 +81,9 @@ * Searches the array for a given key and returns the offset if successful. * @return int|FALSE offset if it is found, FALSE otherwise */ - public static function searchKey($arr, $key) + public static function searchKey(array $arr, $key) { - $foo = array($key => NULL); + $foo = [$key => NULL]; return array_search(key($foo), array_keys($arr), TRUE); } @@ -102,7 +94,7 @@ */ public static function insertBefore(array & $arr, $key, array $inserted) { - $offset = self::searchKey($arr, $key); + $offset = (int) self::searchKey($arr, $key); $arr = array_slice($arr, 0, $offset, TRUE) + $inserted + array_slice($arr, $offset, count($arr), TRUE); } @@ -140,7 +132,7 @@ */ public static function grep(array $arr, $pattern, $flags = 0) { - return Strings::pcre('preg_grep', array($pattern, $arr, $flags)); + return Strings::pcre('preg_grep', [$pattern, $arr, $flags]); } @@ -150,7 +142,7 @@ */ public static function flatten(array $arr, $preserveKeys = FALSE) { - $res = array(); + $res = []; $cb = $preserveKeys ? function ($v, $k) use (& $res) { $res[$k] = $v; } : function ($v) use (& $res) { $res[] = $v; }; @@ -177,13 +169,13 @@ { $parts = is_array($path) ? $path - : preg_split('#(\[\]|->|=|\|)#', $path, NULL, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); - if (!$parts || $parts[0] === '=' || $parts[0] === '|' || $parts === array('->')) { + if (!$parts || $parts[0] === '=' || $parts[0] === '|' || $parts === ['->']) { throw new Nette\InvalidArgumentException("Invalid path '$path'."); } - $res = $parts[0] === '->' ? new \stdClass : array(); + $res = $parts[0] === '->' ? new \stdClass : []; foreach ($arr as $rowOrig) { $row = (array) $rowOrig; @@ -227,7 +219,7 @@ */ public static function normalize(array $arr, $filling = NULL) { - $res = array(); + $res = []; foreach ($arr as $k => $v) { $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; } diff -Nru php-nette-2.3.10/Nette/Utils/Callback.php php-nette-2.4-20160731/Nette/Utils/Callback.php --- php-nette-2.3.10/Nette/Utils/Callback.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Callback.php 2016-07-31 17:46:44.000000000 +0000 @@ -15,6 +15,7 @@ */ class Callback { + use Nette\StaticClass; /** * @param mixed class, object, callable @@ -24,7 +25,7 @@ public static function closure($callable, $m = NULL) { if ($m !== NULL) { - $callable = array($callable, $m); + $callable = [$callable, $m]; } elseif (is_string($callable) && count($tmp = explode('::', $callable)) === 2) { $callable = $tmp; @@ -33,24 +34,20 @@ return $callable; } elseif (is_object($callable)) { - $callable = array($callable, '__invoke'); + $callable = [$callable, '__invoke']; } - if (PHP_VERSION_ID >= 50400) { - if (is_string($callable) && function_exists($callable)) { - $r = new \ReflectionFunction($callable); - return $r->getClosure(); + if (is_string($callable) && function_exists($callable)) { + return (new \ReflectionFunction($callable))->getClosure(); - } elseif (is_array($callable) && method_exists($callable[0], $callable[1])) { - $r = new \ReflectionMethod($callable[0], $callable[1]); - return $r->getClosure($callable[0]); - } + } elseif (is_array($callable) && method_exists($callable[0], $callable[1])) { + return (new \ReflectionMethod($callable[0], $callable[1]))->getClosure($callable[0]); } self::check($callable); $_callable_ = $callable; - return function () use ($_callable_) { - return call_user_func_array($_callable_, func_get_args()); + return function (...$args) use ($_callable_) { + return $_callable_(...$args); }; } @@ -59,10 +56,10 @@ * Invokes callback. * @return mixed */ - public static function invoke($callable) + public static function invoke($callable, ...$args) { self::check($callable); - return call_user_func_array($callable, array_slice(func_get_args(), 1)); + return call_user_func_array($callable, $args); } @@ -70,7 +67,7 @@ * Invokes callback with an array of parameters. * @return mixed */ - public static function invokeArgs($callable, array $args = array()) + public static function invokeArgs($callable, array $args = []) { self::check($callable); return call_user_func_array($callable, $args); @@ -84,9 +81,9 @@ */ public static function invokeSafe($function, array $args, $onError) { - $prev = set_error_handler(function ($severity, $message, $file, $line, $context = NULL, $stack = NULL) use ($onError, & $prev, $function) { + $prev = set_error_handler(function ($severity, $message, $file) use ($onError, & $prev, $function) { if ($file === '' && defined('HHVM_VERSION')) { // https://github.com/facebook/hhvm/issues/4625 - $file = $stack[1]['file']; + $file = func_get_arg(5)[1]['file']; } if ($file === __FILE__) { $msg = preg_replace("#^$function\(.*?\): #", '', $message); @@ -94,11 +91,11 @@ return; } } - return $prev ? call_user_func_array($prev, func_get_args()) : FALSE; + return $prev ? $prev(...func_get_args()) : FALSE; }); try { - $res = call_user_func_array($function, $args); + $res = $function(...$args); restore_error_handler(); return $res; @@ -152,10 +149,11 @@ if ($callable instanceof \Closure) { $callable = self::unwrap($callable); } elseif ($callable instanceof Nette\Callback) { + trigger_error('Nette\Callback is deprecated.', E_USER_DEPRECATED); $callable = $callable->getNative(); } - $class = class_exists('Nette\Reflection\Method') ? 'Nette\Reflection\Method' : 'ReflectionMethod'; + $class = class_exists(Nette\Reflection\Method::class) ? Nette\Reflection\Method::class : 'ReflectionMethod'; if (is_string($callable) && strpos($callable, '::')) { return new $class($callable); } elseif (is_array($callable)) { @@ -163,7 +161,7 @@ } elseif (is_object($callable) && !$callable instanceof \Closure) { return new $class($callable, '__invoke'); } else { - $class = class_exists('Nette\Reflection\GlobalFunction') ? 'Nette\Reflection\GlobalFunction' : 'ReflectionFunction'; + $class = class_exists(Nette\Reflection\GlobalFunction::class) ? Nette\Reflection\GlobalFunction::class : 'ReflectionFunction'; return new $class($callable); } } @@ -191,10 +189,10 @@ return isset($vars['_callable_']) ? $vars['_callable_'] : $closure; } elseif ($obj = $r->getClosureThis()) { - return array($obj, $r->getName()); + return [$obj, $r->getName()]; } elseif ($class = $r->getClosureScopeClass()) { - return array($class->getName(), $r->getName()); + return [$class->getName(), $r->getName()]; } else { return $r->getName(); diff -Nru php-nette-2.3.10/Nette/Utils/DateTime.php php-nette-2.4-20160731/Nette/Utils/DateTime.php --- php-nette-2.3.10/Nette/Utils/DateTime.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/DateTime.php 2016-07-31 17:46:44.000000000 +0000 @@ -13,19 +13,21 @@ /** * DateTime. */ -class DateTime extends \DateTime +class DateTime extends \DateTime implements \JsonSerializable { + use Nette\SmartObject; + /** minute in seconds */ const MINUTE = 60; /** hour in seconds */ - const HOUR = 3600; + const HOUR = 60 * self::MINUTE; /** day in seconds */ - const DAY = 86400; + const DAY = 24 * self::HOUR; /** week in seconds */ - const WEEK = 604800; + const WEEK = 7 * self::DAY; /** average month in seconds */ const MONTH = 2629800; @@ -36,20 +38,19 @@ /** * DateTime object factory. - * @param string|int|\DateTime + * @param string|int|\DateTimeInterface * @return self */ public static function from($time) { - if ($time instanceof \DateTime || $time instanceof \DateTimeInterface) { + if ($time instanceof \DateTimeInterface) { return new static($time->format('Y-m-d H:i:s'), $time->getTimezone()); } elseif (is_numeric($time)) { if ($time <= self::YEAR) { $time += time(); } - $tmp = new static('@' . $time); - return $tmp->setTimeZone(new \DateTimeZone(date_default_timezone_get())); + return (new static('@' . $time))->setTimeZone(new \DateTimeZone(date_default_timezone_get())); } else { // textual or NULL return new static($time); @@ -122,4 +123,14 @@ return $date ? static::from($date) : FALSE; } + + /** + * Returns JSON representation in ISO 8601 (used by JavaScript). + * @return string + */ + public function jsonSerialize() + { + return $this->format('c'); + } + } diff -Nru php-nette-2.3.10/Nette/Utils/FileSystem.php php-nette-2.4-20160731/Nette/Utils/FileSystem.php --- php-nette-2.3.10/Nette/Utils/FileSystem.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/FileSystem.php 2016-07-31 17:46:44.000000000 +0000 @@ -15,6 +15,7 @@ */ class FileSystem { + use Nette\StaticClass; /** * Creates a directory. @@ -45,13 +46,13 @@ } elseif (is_dir($source)) { static::createDir($dest); foreach (new \FilesystemIterator($dest) as $item) { - static::delete($item); + static::delete($item->getPathname()); } foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { if ($item->isDir()) { static::createDir($dest . '/' . $iterator->getSubPathName()); } else { - static::copy($item, $dest . '/' . $iterator->getSubPathName()); + static::copy($item->getPathname(), $dest . '/' . $iterator->getSubPathName()); } } @@ -79,7 +80,7 @@ } elseif (is_dir($path)) { foreach (new \FilesystemIterator($path) as $item) { - static::delete($item); + static::delete($item->getPathname()); } if (!@rmdir($path)) { // @ is escalated to exception throw new Nette\IOException("Unable to delete directory '$path'."); @@ -112,6 +113,21 @@ } + /** + * Reads file content. + * @return string + * @throws Nette\IOException + */ + public static function read($file) + { + $content = @file_get_contents($file); // @ is escalated to exception + if ($content === FALSE) { + throw new Nette\IOException("Unable to read file '$file'."); + } + return $content; + } + + /** * Writes a string to a file. * @return void diff -Nru php-nette-2.3.10/Nette/Utils/Html.php php-nette-2.4-20160731/Nette/Utils/Html.php --- php-nette-2.3.10/Nette/Utils/Html.php 2016-04-13 18:50:46.000000000 +0000 +++ php-nette-2.4-20160731/Nette/Utils/Html.php 2016-07-31 17:46:44.000000000 +0000 @@ -21,8 +21,10 @@ * echo $el->startTag(), $el->endTag(); *Nette Framework Requirements Checker - - - - - - - --- - diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/denied/checker.js php-nette-2.4-20160731/tools/Requirements-Checker/assets/denied/checker.js --- php-nette-2.3.10/tools/Requirements-Checker/assets/denied/checker.js 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/denied/checker.js 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -fileProtectionChecker = true; diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/denied/.htaccess php-nette-2.4-20160731/tools/Requirements-Checker/assets/denied/.htaccess --- php-nette-2.3.10/tools/Requirements-Checker/assets/denied/.htaccess 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/denied/.htaccess 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -Order Allow,Deny -Deny from all diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/denied/web.config php-nette-2.4-20160731/tools/Requirements-Checker/assets/denied/web.config --- php-nette-2.3.10/tools/Requirements-Checker/assets/denied/web.config 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/denied/web.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - -Nette Framework Requirements Checker
- -This script checks if your server and PHP configuration meets the requirements - for running Nette Framework. It checks version of PHP, - if appropriate PHP extensions have been loaded, and if PHP directives are set correctly.
- - --- -Sorry, your server configuration does not satisfy the requirements of Nette Framework.
--- - - -Congratulations! Server configuration meets the minimum requirements for Nette Framework.
-Please see the warnings listed below.
-Details
- -- $requirement):?> - passed) ? ($requirement->passed ? 'passed' : ($requirement->required ? 'failed' : 'warning')) : 'info' ?> -
- -- - - description)): ?> -title) ?> - - passed) && isset($requirement->errorMessage)): ?> -errorMessage) ?> - message)): ?> -message) ?> - passed)): ?> -passed ? 'Enabled' : 'Disabled' ?> - -Not tested - -- - - - script)): ?> - script ?> - - - -description ?> -Please check the error messages and try again.
-- \ No newline at end of file Binary files /tmp/tmpdZCFV9/WT0Stc0_Wo/php-nette-2.3.10/tools/Requirements-Checker/assets/failed.gif and /tmp/tmpdZCFV9/izdFZJ78Jb/php-nette-2.4-20160731/tools/Requirements-Checker/assets/failed.gif differ Binary files /tmp/tmpdZCFV9/WT0Stc0_Wo/php-nette-2.3.10/tools/Requirements-Checker/assets/info.gif and /tmp/tmpdZCFV9/izdFZJ78Jb/php-nette-2.4-20160731/tools/Requirements-Checker/assets/info.gif differ Binary files /tmp/tmpdZCFV9/WT0Stc0_Wo/php-nette-2.3.10/tools/Requirements-Checker/assets/logo.png and /tmp/tmpdZCFV9/izdFZJ78Jb/php-nette-2.4-20160731/tools/Requirements-Checker/assets/logo.png differ Binary files /tmp/tmpdZCFV9/WT0Stc0_Wo/php-nette-2.3.10/tools/Requirements-Checker/assets/passed.gif and /tmp/tmpdZCFV9/izdFZJ78Jb/php-nette-2.4-20160731/tools/Requirements-Checker/assets/passed.gif differ diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/rewrite/checker.js php-nette-2.4-20160731/tools/Requirements-Checker/assets/rewrite/checker.js --- php-nette-2.3.10/tools/Requirements-Checker/assets/rewrite/checker.js 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/rewrite/checker.js 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -modRewriteChecker = true; diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/rewrite/.htaccess php-nette-2.4-20160731/tools/Requirements-Checker/assets/rewrite/.htaccess --- php-nette-2.3.10/tools/Requirements-Checker/assets/rewrite/.htaccess 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/rewrite/.htaccess 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -RewriteEngine On -RewriteRule .* checker.js [L] diff -Nru php-nette-2.3.10/tools/Requirements-Checker/assets/rewrite/web.config php-nette-2.4-20160731/tools/Requirements-Checker/assets/rewrite/web.config --- php-nette-2.3.10/tools/Requirements-Checker/assets/rewrite/web.config 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/assets/rewrite/web.config 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ - -- -- -- -- -- - -- - - \ No newline at end of file Binary files /tmp/tmpdZCFV9/WT0Stc0_Wo/php-nette-2.3.10/tools/Requirements-Checker/assets/warning.gif and /tmp/tmpdZCFV9/izdFZJ78Jb/php-nette-2.4-20160731/tools/Requirements-Checker/assets/warning.gif differ diff -Nru php-nette-2.3.10/tools/Requirements-Checker/checker.php php-nette-2.4-20160731/tools/Requirements-Checker/checker.php --- php-nette-2.3.10/tools/Requirements-Checker/checker.php 2016-04-13 18:50:22.000000000 +0000 +++ php-nette-2.4-20160731/tools/Requirements-Checker/checker.php 1970-01-01 00:00:00.000000000 +0000 @@ -1,297 +0,0 @@ - 'Web server', - 'message' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'unknown', -); - -$tests[] = array( - 'title' => 'PHP version', - 'required' => TRUE, - 'passed' => version_compare(PHP_VERSION, '5.3.1', '>='), - 'message' => PHP_VERSION, - 'description' => 'Your PHP version is too old. Nette Framework requires at least PHP 5.3.1 or higher.', -); - -$tests[] = array( - 'title' => 'Memory limit', - 'message' => ini_get('memory_limit'), -); - -if (!isset($_SERVER['SERVER_SOFTWARE']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== FALSE) { - $tests['hf'] = array( - 'title' => '.htaccess file protection', - 'required' => FALSE, - 'description' => 'File protection by- -- -- -- -- - .htaccess
is not present. You must be careful to put files into document_root folder.', - 'script' => ' ', - ); - - $tests['hr'] = array( - 'title' => '.htaccess mod_rewrite', - 'required' => FALSE, - 'description' => 'Mod_rewrite is probably not present. You will not be able to use Cool URL.', - 'script' => ' ', - ); -} - -$tests[] = array( - 'title' => 'Function ini_set()', - 'required' => FALSE, - 'passed' => function_exists('ini_set'), - 'description' => 'Functionini_set()
is disabled. Some parts of Nette Framework may not work properly.', -); - -$tests[] = array( - 'title' => 'Function error_reporting()', - 'required' => TRUE, - 'passed' => function_exists('error_reporting'), - 'description' => 'Functionerror_reporting()
is disabled. Nette Framework requires this to be enabled.', -); - -$tests[] = array( - 'title' => 'Function flock()', - 'required' => TRUE, - 'passed' => flock(fopen(__FILE__, 'r'), LOCK_SH), - 'description' => 'Functionflock()
is not supported on this filesystem. Nette Framework requires this to process atomic file operations.', -); - -$tests[] = array( - 'title' => 'Register_globals', - 'required' => TRUE, - 'passed' => !iniFlag('register_globals'), - 'message' => 'Disabled', - 'errorMessage' => 'Enabled', - 'description' => 'Configuration directiveregister_globals
is enabled. Nette Framework requires this to be disabled.', -); - -$tests[] = array( - 'title' => 'Variables_order', - 'required' => TRUE, - 'passed' => strpos(ini_get('variables_order'), 'G') !== FALSE && strpos(ini_get('variables_order'), 'P') !== FALSE && strpos(ini_get('variables_order'), 'C') !== FALSE, - 'description' => 'Configuration directivevariables_order
is missing. Nette Framework requires this to be set.', -); - -$tests[] = array( - 'title' => 'Session auto-start', - 'required' => FALSE, - 'passed' => session_id() === '' && !defined('SID'), - 'description' => 'Session auto-start is enabled. Nette Framework recommends not to use this directive for security reasons.', -); - -$tests[] = array( - 'title' => 'PCRE with UTF-8 support', - 'required' => TRUE, - 'passed' => @preg_match('/pcre/u', 'pcre'), - 'description' => 'PCRE extension must support UTF-8.', -); - -$reflection = new ReflectionFunction('paint'); -$tests[] = array( - 'title' => 'Reflection phpDoc', - 'required' => TRUE, - 'passed' => strpos($reflection->getDocComment(), 'Paints') !== FALSE, - 'description' => 'Reflection phpDoc are not available (probably due to an eAccelerator bug). You cannot use @annotations.', -); - -$tests[] = array( - 'title' => 'ICONV extension', - 'required' => TRUE, - 'passed' => extension_loaded('iconv') && (ICONV_IMPL !== 'unknown') && @iconv('UTF-16', 'UTF-8//IGNORE', iconv('UTF-8', 'UTF-16//IGNORE', 'test')) === 'test', - 'message' => 'Enabled and works properly', - 'errorMessage' => 'Disabled or does not work properly', - 'description' => 'ICONV extension is required and must work properly.', -); - -$tests[] = array( - 'title' => 'JSON extension', - 'required' => TRUE, - 'passed' => extension_loaded('json'), -); - -$tests[] = array( - 'title' => 'Fileinfo extension', - 'required' => FALSE, - 'passed' => extension_loaded('fileinfo'), - 'description' => 'Fileinfo extension is absent. You will not be able to detect content-type of uploaded files.', -); - -$tests[] = array( - 'title' => 'PHP tokenizer', - 'required' => TRUE, - 'passed' => extension_loaded('tokenizer'), - 'description' => 'PHP tokenizer is required.', -); - -$tests[] = array( - 'title' => 'PDO extension', - 'required' => FALSE, - 'passed' => $pdo = extension_loaded('pdo') && PDO::getAvailableDrivers(), - 'message' => $pdo ? 'Available drivers: ' . implode(' ', PDO::getAvailableDrivers()) : NULL, - 'description' => 'PDO extension or PDO drivers are absent. You will not be able to useNette\Database
.', -); - -$tests[] = array( - 'title' => 'Multibyte String extension', - 'required' => FALSE, - 'passed' => extension_loaded('mbstring'), - 'description' => 'Multibyte String extension is absent. Some internationalization components may not work properly.', -); - -$tests[] = array( - 'title' => 'Multibyte String function overloading', - 'required' => TRUE, - 'passed' => !extension_loaded('mbstring') || !(mb_get_info('func_overload') & 2), - 'message' => 'Disabled', - 'errorMessage' => 'Enabled', - 'description' => 'Multibyte String function overloading is enabled. Nette Framework requires this to be disabled. If it is enabled, some string function may not work properly.', -); - -$tests[] = array( - 'title' => 'Memcache extension', - 'required' => FALSE, - 'passed' => extension_loaded('memcache'), - 'description' => 'Memcache extension is absent. You will not be able to useNette\Caching\Storages\MemcachedStorage
.', -); - -$tests[] = array( - 'title' => 'GD extension', - 'required' => FALSE, - 'passed' => extension_loaded('gd'), - 'description' => 'GD extension is absent. You will not be able to useNette\Image
.', -); - -$tests[] = array( - 'title' => 'Bundled GD extension', - 'required' => FALSE, - 'passed' => extension_loaded('gd') && GD_BUNDLED, - 'description' => 'Bundled GD extension is absent. You will not be able to use some functions such asNette\Image::filter()
orNette\Image::rotate()
.', -); - -$tests[] = array( - 'title' => 'Fileinfo extension or mime_content_type()', - 'required' => FALSE, - 'passed' => extension_loaded('fileinfo') || function_exists('mime_content_type'), - 'description' => 'Fileinfo extension or functionmime_content_type()
are absent. You will not be able to determine mime type of uploaded files.', -); - -$tests[] = array( - 'title' => 'Intl extension', - 'required' => FALSE, - 'passed' => class_exists('Transliterator', FALSE), - 'description' => 'Class Transliterator is absent, the output of Nette\Utils\Strings::webalize() and Nette\Utils\Strings::toAscii() may not be accurate for non-latin alphabets.', -); - -$tests[] = array( - 'title' => 'HTTP_HOST or SERVER_NAME', - 'required' => TRUE, - 'passed' => isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']), - 'message' => 'Present', - 'errorMessage' => 'Absent', - 'description' => 'Either$_SERVER["HTTP_HOST"]
or$_SERVER["SERVER_NAME"]
must be available for resolving host name.', -); - -$tests[] = array( - 'title' => 'REQUEST_URI or ORIG_PATH_INFO', - 'required' => TRUE, - 'passed' => isset($_SERVER['REQUEST_URI']) || isset($_SERVER['ORIG_PATH_INFO']), - 'message' => 'Present', - 'errorMessage' => 'Absent', - 'description' => 'Either$_SERVER["REQUEST_URI"]
or$_SERVER["ORIG_PATH_INFO"]
must be available for resolving request URL.', -); - -$tests[] = array( - 'title' => 'SCRIPT_NAME or DOCUMENT_ROOT & SCRIPT_FILENAME', - 'required' => TRUE, - 'passed' => isset($_SERVER['SCRIPT_NAME']) || isset($_SERVER['DOCUMENT_ROOT'], $_SERVER['SCRIPT_FILENAME']), - 'message' => 'Present', - 'errorMessage' => 'Absent', - 'description' => '$_SERVER["SCRIPT_NAME"]
or$_SERVER["DOCUMENT_ROOT"]
with$_SERVER["SCRIPT_FILENAME"]
must be available for resolving script file path.', -); - -$tests[] = array( - 'title' => 'REMOTE_ADDR or php_uname("n")', - 'required' => TRUE, - 'passed' => isset($_SERVER['REMOTE_ADDR']) || function_exists('php_uname'), - 'message' => 'Present', - 'errorMessage' => 'Absent', - 'description' => '$_SERVER["REMOTE_ADDR"]
orphp_uname("n")
must be available for detecting development / production mode.', -); - -paint($tests); - - - -/** - * Paints checker. - * @param array - * @return void - */ -function paint($requirements) -{ - $redirect = round(time(), -1); - if (!isset($_GET) || (isset($_GET['r']) && $_GET['r'] == $redirect)) { - $redirect = NULL; - } - - $errors = $warnings = FALSE; - - foreach ($requirements as $id => $requirement) - { - $requirements[$id] = $requirement = (object) $requirement; - if (isset($requirement->passed) && !$requirement->passed) { - if ($requirement->required) { - $errors = TRUE; - } else { - $warnings = TRUE; - } - } - } - - require TEMPLATE_FILE; -} - - - -/** - * Gets the boolean value of a configuration option. - * @param string configuration option name - * @return bool - */ -function iniFlag($var) -{ - $status = strtolower(ini_get($var)); - return $status === 'on' || $status === 'true' || $status === 'yes' || (int) $status; -} diff -Nru php-nette-2.3.10/version.txt php-nette-2.4-20160731/version.txt --- php-nette-2.3.10/version.txt 2016-04-13 18:50:48.000000000 +0000 +++ php-nette-2.4-20160731/version.txt 2016-07-31 17:46:46.000000000 +0000 @@ -1 +1,23 @@ -Nette 2.3.10 (revision 79d539f released on 2016-04-13) \ No newline at end of file +Nette 2.4-20160731 + +latte/latte v2.4.1 Latte: the amazing template engine for PHP +nette/application v2.4.1 Nette Application MVC Component +nette/bootstrap v2.4.1 Nette Bootstrap +nette/caching v2.5.1 Nette Caching Component +nette/component-model v2.3.0 Nette Component Model +nette/database v2.4.0 Nette Database Component +nette/deprecated v2.4.0 APIs and features removed from Nette Framework +nette/di v2.4.2 Nette Dependency Injection Component +nette/finder v2.4.0 Nette Finder: Files Searching +nette/forms v2.4.1 Nette Forms: greatly facilitates web forms +nette/http v2.4.0 Nette HTTP Component +nette/mail v2.4.1 Nette Mail: Sending E-mails +nette/neon v2.4.0 Nette NEON: parser & generator for Nette Object Notation +nette/php-generator v2.4.1 Nette PHP Generator +nette/reflection v2.4.0 Nette PHP Reflection Component +nette/robot-loader v2.4.0 Nette RobotLoader: comfortable autoloading +nette/safe-stream v2.3.2 Nette SafeStream: Atomic Operations +nette/security v2.4.0 Nette Security: Access Control Component +nette/tokenizer v2.2.3 Nette Tokenizer +nette/utils v2.4.0 Nette Utility Classes +tracy/tracy v2.4.2 Tracy: useful PHP debugger