diff --git a/.travis.yml b/.travis.yml index 2add223..ebc42ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,6 @@ php: - 5.4 - 5.5 -env: - - CUBRID_VERSION=9.1.0 - services: - redis-server - memcached diff --git a/README.md b/README.md index 1cd0121..dc03ac7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ DOCUMENTATION For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md) to have a general idea of what has changed in 2.0. -We are writing more documentation to get you started and learn more in depth. +[Definitive Guide draft](docs/guide/index.md) is available. It's not complete yet but main parts are already OK. HOW TO PARTICIPATE diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index 1196280..e243223 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -61,7 +61,7 @@ class SiteController extends Controller { $model = new LoginForm(); if ($model->load($_POST) && $model->login()) { - return $this->goHome(); + return $this->goBack(); } else { return $this->render('login', array( 'model' => $model, diff --git a/docs/guide/active-record.md b/docs/guide/active-record.md index fc98f98..7d0c744 100644 --- a/docs/guide/active-record.md +++ b/docs/guide/active-record.md @@ -175,7 +175,9 @@ $customer->delete(); Customer::updateAllCounters(array('age' => 1)); ``` -Notice that you can always use the `save` method, and ActiveRecord will automatically perform an INSERT for new records and an UPDATE for existing ones. +> Info: The `save()` method will either perform an `INSERT` or `UPDATE` SQL statement, depending + on whether the ActiveRecord being saved is new or not by checking `ActiveRecord::isNewRecord`. + Data Input and Validation ------------------------- diff --git a/docs/guide/caching.md b/docs/guide/caching.md index 0624d78..64c7e8d 100644 --- a/docs/guide/caching.md +++ b/docs/guide/caching.md @@ -60,7 +60,8 @@ is a summary of the available cache components: the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load balancers, etc.) -* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) NoSQL database. +* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) key-value store + (redis version 2.6 or higher is required). * [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) ([see also](http://php.net/manual/en/book.wincache.php)) extension. diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index f9b6285..8325855 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -1,8 +1,8 @@ Configuration ============= -In Yii application and majority of components have sensible defaults so it's unlikely you spend lots of time configuring -it. Still there are some mandatory options such as database connection you should set up. +Yii applications rely upon components to perform most of the common tasks, such as connecting to a database, routing browser requests, and handling sessions. How these stock components behave can be adjusted by *configuring* your Yii application. The majority of components have sensible defaults, so it's unlikely that you'll spend a lot of time configuring +them. Still there are some mandatory settings, such as the database connection, that you will have to establish. How application is configured depends on application template but there are some general principles applying in any case. diff --git a/docs/guide/controller.md b/docs/guide/controller.md index 2874c66..c665037 100644 --- a/docs/guide/controller.md +++ b/docs/guide/controller.md @@ -35,6 +35,9 @@ class SiteController extends Controller ``` As you can see, typical controller contains actions that are public class methods named as `actionSomething`. +The output of an action is what the method returns. The return value will be handled by the `response` application +component which can convert the output to differnet formats such as JSON for example. The default behavior +is to output the value unchanged though. Routes ------ @@ -183,7 +186,6 @@ Filters Catching all incoming requests ------------------------------ - See also -------- diff --git a/docs/guide/security.md b/docs/guide/security.md index af30e5b..a7f4054 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -6,24 +6,23 @@ Hashing and verifying passwords Most developers know that you cannot store passwords in plain text, but many believe it's safe to hash passwords using `md5` or `sha1`. There was a time when those hashing algorithms were sufficient, but modern hardware makes it possible to break those hashes very quickly using a brute force attack. -In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is bcrypt. In PHP, you can create a bcrypt hash by using [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions for generating hash from -password and verifying existing hash. +In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is `bcrypt`. In PHP, you can create a `bcrypt` hash by using the [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions to make securely generating and verifying hashes easier. -When user sets his password we're taking password string from POST and then getting a hash: +When a user provides a password for the first time (e.g., upon registration), the password needs to be hashed: ```php $hash = \yii\helpers\Security::generatePasswordHash($password); ``` -The hash we've got is persisted to database to be used later. +The hash would then be associated with the model, so that it will be stored in the database for later use. -Then when user is trying to log in we're verifying the password he entered against a hash that we've previously persisted: +When user attempts to log in, the submitted log in password must be verified against the previously hashed and stored password: ```php -if(Security::validatePassword($password, $hash)) { +use \yii\helpers; +if (Security::validatePassword($password, $hash)) { // all good, logging user in -} -else { +} else { // wrong password } ``` diff --git a/docs/guide/validation.md b/docs/guide/validation.md index 0322573..c555913 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -38,7 +38,7 @@ Compares the specified attribute value with another value and validates if they Verifies if the attribute represents a date, time or datetime in a proper format. -- `format` the date format that the value being validated should follow accodring to [[http://www.php.net/manual/en/datetime.createfromformat.php]]. _('Y-m-d')_ +- `format` the date format that the value being validated should follow according to [[http://www.php.net/manual/en/datetime.createfromformat.php]]. _('Y-m-d')_ - `timestampAttribute` the name of the attribute to receive the parsing result. ### `default`: [[DefaultValueValidator]] @@ -179,4 +179,4 @@ if ($validator->validateValue($email)) { } ``` -TBD: refer to http://www.yiiframework.com/wiki/56/ for the format \ No newline at end of file +TBD: refer to http://www.yiiframework.com/wiki/56/ for the format diff --git a/extensions/composer/composer.json b/extensions/composer/composer.json index d8cf49d..db430a2 100644 --- a/extensions/composer/composer.json +++ b/extensions/composer/composer.json @@ -22,6 +22,6 @@ "yiisoft/yii2": "*" }, "autoload": { - "psr-0": { "yii\\composer": "" } + "psr-0": { "yii\\composer\\": "" } } } diff --git a/extensions/composer/yii/composer/Installer.php b/extensions/composer/yii/composer/Installer.php new file mode 100644 index 0000000..8b5b9ea --- /dev/null +++ b/extensions/composer/yii/composer/Installer.php @@ -0,0 +1,208 @@ + + * @since 2.0 + */ +class Installer extends LibraryInstaller +{ + const EXTRA_WRITABLES = 'yii-writables'; + const EXTRA_EXECUTABLES = 'yii-executables'; + const EXTRA_CONFIG = 'yii-config'; + const EXTRA_COMMANDS = 'yii-commands'; + const EXTRA_ALIASES = 'yii-aliases'; + const EXTRA_PREINIT = 'yii-preinit'; + const EXTRA_INIT = 'yii-init'; + + /** + * @inheritdoc + */ + public function supports($packageType) + { + return $packageType === 'yii2-extension'; + } + + /** + * @inheritdoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::install($repo, $package); + $this->addPackage($package); + } + + /** + * @inheritdoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + parent::update($repo, $initial, $target); + $this->removePackage($initial); + $this->addPackage($target); + } + + /** + * @inheritdoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::uninstall($repo, $package); + $this->removePackage($package); + } + + protected function addPackage(PackageInterface $package) + { + $extension = array('name' => $package->getPrettyName()); + + $root = $package->getPrettyName(); + if ($targetDir = $package->getTargetDir()) { + $root .= '/' . trim($targetDir, '/'); + } + $root = trim($root, '/'); + + $extra = $package->getExtra(); + + if (isset($extra[self::EXTRA_PREINIT]) && is_string($extra[self::EXTRA_PREINIT])) { + $extension[self::EXTRA_PREINIT] = "/$root/" . ltrim(str_replace('\\', '/', $extra[self::EXTRA_PREINIT]), '/'); + } + if (isset($extra[self::EXTRA_INIT]) && is_string($extra[self::EXTRA_INIT])) { + $extension[self::EXTRA_INIT] = "/$root/" . ltrim(str_replace('\\', '/', $extra[self::EXTRA_INIT]), '/'); + } + + if (isset($extra['aliases']) && is_array($extra['aliases'])) { + foreach ($extra['aliases'] as $alias => $path) { + $extension['aliases']['@' . ltrim($alias, '@')] = "/$root/" . ltrim(str_replace('\\', '/', $path), '/'); + } + } + + if (!empty($aliases)) { + foreach ($aliases as $alias => $path) { + if (strncmp($alias, '@', 1) !== 0) { + $alias = '@' . $alias; + } + $path = trim(str_replace('\\', '/', $path), '/'); + $extension['aliases'][$alias] = $root . '/' . $path; + } + } + + $extensions = $this->loadExtensions(); + $extensions[$package->getId()] = $extension; + $this->saveExtensions($extensions); + } + + protected function removePackage(PackageInterface $package) + { + $packages = $this->loadExtensions(); + unset($packages[$package->getId()]); + $this->saveExtensions($packages); + } + + protected function loadExtensions() + { + $file = $this->vendorDir . '/yii-extensions.php'; + if (!is_file($file)) { + return array(); + } + $extensions = require($file); + /** @var string $vendorDir defined in yii-extensions.php */ + $n = strlen($vendorDir); + foreach ($extensions as &$extension) { + if (isset($extension['aliases'])) { + foreach ($extension['aliases'] as $alias => $path) { + $extension['aliases'][$alias] = '' . substr($path, $n); + } + } + if (isset($extension[self::EXTRA_PREINIT])) { + $extension[self::EXTRA_PREINIT] = '' . substr($extension[self::EXTRA_PREINIT], $n); + } + if (isset($extension[self::EXTRA_INIT])) { + $extension[self::EXTRA_INIT] = '' . substr($extension[self::EXTRA_INIT], $n); + } + } + return $extensions; + } + + protected function saveExtensions(array $extensions) + { + $file = $this->vendorDir . '/yii-extensions.php'; + $array = str_replace("'", '$vendorDir . \'', var_export($extensions, true)); + file_put_contents($file, " array(), + self::EXTRA_EXECUTABLES => array(), + ), $event->getComposer()->getPackage()->getExtra()); + + foreach ((array)$options[self::EXTRA_WRITABLES] as $path) { + echo "Setting writable: $path ..."; + if (is_dir($path)) { + chmod($path, 0777); + echo "done\n"; + } else { + echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path; + return; + } + } + + foreach ((array)$options[self::EXTRA_EXECUTABLES] as $path) { + echo "Setting executable: $path ..."; + if (is_file($path)) { + chmod($path, 0755); + echo "done\n"; + } else { + echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n"; + return; + } + } + } + + /** + * Executes a yii command. + * @param CommandEvent $event + */ + public static function run($event) + { + $options = array_merge(array( + self::EXTRA_COMMANDS => array(), + ), $event->getComposer()->getPackage()->getExtra()); + + if (!isset($options[self::EXTRA_CONFIG])) { + throw new Exception('Please specify the "' . self::EXTRA_CONFIG . '" parameter in composer.json.'); + } + $configFile = getcwd() . '/' . $options[self::EXTRA_CONFIG]; + if (!is_file($configFile)) { + throw new Exception("Config file does not exist: $configFile"); + } + + require_once(__DIR__ . '/../../../yii2/yii/Yii.php'); + $application = new Application(require($configFile)); + $request = $application->getRequest(); + + foreach ((array)$options[self::EXTRA_COMMANDS] as $command) { + $params = str_getcsv($command, ' '); // see http://stackoverflow.com/a/6609509/291573 + $request->setParams($params); + list($route, $params) = $request->resolve(); + echo "Running command: yii {$command}\n"; + $application->runAction($route, $params); + } + } +} diff --git a/extensions/composer/yii/composer/InstallerPlugin.php b/extensions/composer/yii/composer/InstallerPlugin.php new file mode 100644 index 0000000..f6de6b2 --- /dev/null +++ b/extensions/composer/yii/composer/InstallerPlugin.php @@ -0,0 +1,30 @@ + + * @since 2.0 + */ +class InstallerPlugin implements PluginInterface +{ + /** + * @inheritdoc + */ + public function activate(Composer $composer, IOInterface $io) + { + $installer = new Installer($io, $composer); + $composer->getInstallationManager()->addInstaller($installer); + } +} diff --git a/extensions/jui/composer.json b/extensions/jui/composer.json index f5e259f..e228973 100644 --- a/extensions/jui/composer.json +++ b/extensions/jui/composer.json @@ -16,6 +16,6 @@ "yiisoft/yii2": "*" }, "autoload": { - "psr-0": { "yii\\jui": "" } + "psr-0": { "yii\\jui\\": "" } } } diff --git a/extensions/jui/yii/jui/DatePicker.php b/extensions/jui/yii/jui/DatePicker.php index c275190..1d3012d 100644 --- a/extensions/jui/yii/jui/DatePicker.php +++ b/extensions/jui/yii/jui/DatePicker.php @@ -9,6 +9,7 @@ namespace yii\jui; use Yii; use yii\helpers\Html; +use yii\helpers\Json; /** * DatePicker renders an datepicker jQuery UI widget. @@ -61,11 +62,19 @@ class DatePicker extends InputWidget public function run() { echo $this->renderWidget() . "\n"; - $this->registerWidget('datepicker', DatePickerAsset::className()); if ($this->language !== false) { $view = $this->getView(); DatePickerRegionalAsset::register($view); - $view->registerJs("$('#{$this->options['id']}').datepicker('option', $.datepicker.regional['{$this->language}']);"); + + $options = Json::encode($this->clientOptions); + $view->registerJs("$('#{$this->options['id']}').datepicker($.extend({}, $.datepicker.regional['{$this->language}'], $options));"); + + $options = $this->clientOptions; + $this->clientOptions = false; // the datepicker js widget is already registered + $this->registerWidget('datepicker', DatePickerAsset::className()); + $this->clientOptions = $options; + } else { + $this->registerWidget('datepicker', DatePickerAsset::className()); } } diff --git a/extensions/mutex/composer.json b/extensions/mutex/composer.json index db8cde3..2fb95c4 100644 --- a/extensions/mutex/composer.json +++ b/extensions/mutex/composer.json @@ -22,6 +22,6 @@ "yiisoft/yii2": "*" }, "autoload": { - "psr-0": { "yii\\mutex": "" } + "psr-0": { "yii\\mutex\\": "" } } } diff --git a/extensions/smarty/composer.json b/extensions/smarty/composer.json index b1e1681..333e76e 100644 --- a/extensions/smarty/composer.json +++ b/extensions/smarty/composer.json @@ -20,9 +20,9 @@ "minimum-stability": "dev", "require": { "yiisoft/yii2": "*", - "smarty/smarty": "v3.1.13" + "smarty/smarty": ">=v3.1.13" }, "autoload": { - "psr-0": { "yii\\smarty": "" } + "psr-0": { "yii\\smarty\\": "" } } } diff --git a/extensions/twig/composer.json b/extensions/twig/composer.json index 0c1644a..ba21065 100644 --- a/extensions/twig/composer.json +++ b/extensions/twig/composer.json @@ -23,6 +23,6 @@ "twig/twig": "1.13.*" }, "autoload": { - "psr-0": { "yii\\twig": "" } + "psr-0": { "yii\\twig\\": "" } } } diff --git a/extensions/twig/yii/twig/ViewRenderer.php b/extensions/twig/yii/twig/ViewRenderer.php index 06ad2d2..860a2f2 100644 --- a/extensions/twig/yii/twig/ViewRenderer.php +++ b/extensions/twig/yii/twig/ViewRenderer.php @@ -7,7 +7,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\renderers; +namespace yii\twig; use Yii; use yii\base\View; diff --git a/framework/composer.json b/framework/composer.json index 89d6064..c5ff072 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -74,8 +74,6 @@ "psr-0": { "yii\\": "/" } }, "suggest": { - "michelf/php-markdown": "Required by Markdown.", - "twig/twig": "Required by TwigViewRenderer.", - "smarty/smarty": "Required by SmartyViewRenderer." + "michelf/php-markdown": "Required by Markdown." } } diff --git a/framework/yii/BaseYii.php b/framework/yii/BaseYii.php index b586160..cf94166 100644 --- a/framework/yii/BaseYii.php +++ b/framework/yii/BaseYii.php @@ -541,7 +541,7 @@ class BaseYii public static function t($category, $message, $params = array(), $language = null) { if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + return self::$app->getI18n()->translate($category, $message, $params, $language ?: self::$app->language); } else { return is_array($params) ? strtr($message, $params) : $message; } diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js index 17ea8a7..2cb3b90 100644 --- a/framework/yii/assets/yii.activeForm.js +++ b/framework/yii/assets/yii.activeForm.js @@ -345,7 +345,7 @@ var $container = $form.find(attribute.container); var $error = $container.find(attribute.error); if (hasError) { - $error.html(messages[attribute.name][0]); + $error.text(messages[attribute.name][0]); $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass) .addClass(data.settings.errorCssClass); } else { diff --git a/framework/yii/assets/yii.gridView.js b/framework/yii/assets/yii.gridView.js index b07ece2..a452c17 100644 --- a/framework/yii/assets/yii.gridView.js +++ b/framework/yii/assets/yii.gridView.js @@ -22,6 +22,8 @@ }; var defaults = { + filterUrl: undefined, + filterSelector: undefined }; var methods = { @@ -32,6 +34,32 @@ $e.data('yiiGridView', { settings: settings }); + + var enterPressed = false; + $(document).on('change.yiiGridView keydown.yiiGridView', settings.filterSelector, function (event) { + if (event.type === 'keydown') { + if (event.keyCode !== 13) { + return; // only react to enter key + } else { + enterPressed = true; + } + } else { + // prevent processing for both keydown and change events + if (enterPressed) { + enterPressed = false; + return; + } + } + var data = $(settings.filterSelector).serialize(); + var url = settings.filterUrl; + if (url.indexOf('?') >= 0) { + url += '&' + data; + } else { + url += '?' + data; + } + window.location.href = url; + return false; + }); }); }, @@ -74,5 +102,38 @@ return this.data('yiiGridView'); } }; + + var enterPressed = false; + + var filterChanged = function (event) { + if (event.type === 'keydown') { + if (event.keyCode !== 13) { + return; // only react to enter key + } else { + enterPressed = true; + } + } else { + // prevent processing for both keydown and change events + if (enterPressed) { + enterPressed = false; + return; + } + } + var data = $(settings.filterSelector).serialize(); + if (settings.pageVar !== undefined) { + data += '&' + settings.pageVar + '=1'; + } + if (settings.enableHistory && settings.ajaxUpdate !== false && window.History.enabled) { + // Ajaxify this link + var url = $('#' + id).yiiGridView('getUrl'), + params = $.deparam.querystring($.param.querystring(url, data)); + + delete params[settings.ajaxVar]; + window.History.pushState(null, document.title, decodeURIComponent($.param.querystring(url.substr(0, url.indexOf('?')), params))); + } else { + $('#' + id).yiiGridView('update', {data: data}); + } + return false; + }; })(window.jQuery); diff --git a/framework/yii/assets/yii.validation.js b/framework/yii/assets/yii.validation.js index 015040e..3ce9edb 100644 --- a/framework/yii/assets/yii.validation.js +++ b/framework/yii/assets/yii.validation.js @@ -16,6 +16,10 @@ yii.validation = (function ($) { || value === '' || trim && $.trim(value) === ''; }; + var addMessage = function (messages, message, value) { + messages.push(message.replace(/\{value\}/g, value)); + }; + return { required: function (value, messages, options) { var valid = false; @@ -28,7 +32,7 @@ yii.validation = (function ($) { } if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -40,7 +44,7 @@ yii.validation = (function ($) { || options.strict && (value === options.trueValue || value === options.falseValue); if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -50,18 +54,18 @@ yii.validation = (function ($) { } if (typeof value !== 'string') { - messages.push(options.message); + addMessage(messages, options.message, value); return; } if (options.min !== undefined && value.length < options.min) { - messages.push(options.tooShort); + addMessage(messages, options.tooShort, value); } if (options.max !== undefined && value.length > options.max) { - messages.push(options.tooLong); + addMessage(messages, options.tooLong, value); } if (options.is !== undefined && value.length != options.is) { - messages.push(options.is); + addMessage(messages, options.is, value); } }, @@ -71,15 +75,15 @@ yii.validation = (function ($) { } if (typeof value === 'string' && !value.match(options.pattern)) { - messages.push(options.message); + addMessage(messages, options.message, value); return; } if (options.min !== undefined && value < options.min) { - messages.push(options.tooSmall); + addMessage(messages, options.tooSmall, value); } if (options.max !== undefined && value > options.max) { - messages.push(options.tooBig); + addMessage(messages, options.tooBig, value); } }, @@ -91,7 +95,7 @@ yii.validation = (function ($) { || options.not && $.inArray(value, options.range) == -1; if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -101,7 +105,7 @@ yii.validation = (function ($) { } if (!options.not && !value.match(options.pattern) || options.not && value.match(options.pattern)) { - messages.push(options.message) + addMessage(messages, options.message, value); } }, @@ -123,7 +127,7 @@ yii.validation = (function ($) { } if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -149,7 +153,7 @@ yii.validation = (function ($) { } if (!valid || !value.match(options.pattern)) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -170,7 +174,7 @@ yii.validation = (function ($) { h += v.charCodeAt(i); } if (h != hash) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -210,10 +214,13 @@ yii.validation = (function ($) { case '<=': valid = value <= compareValue; break; + default: + valid = false; + break; } if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } } }; diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index e8c496c..683b9ce 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -383,7 +383,7 @@ abstract class Application extends Module * Returns the internationalization (i18n) component * @return \yii\i18n\I18N the internationalization component */ - public function getI18N() + public function getI18n() { return $this->getComponent('i18n'); } @@ -450,7 +450,11 @@ abstract class Application extends Module $msg .= "\nPrevious exception:\n"; $msg .= (string)$exception; if (YII_DEBUG) { - echo $msg; + if (PHP_SAPI === 'cli') { + echo $msg . "\n"; + } else { + echo '
' . htmlspecialchars($msg, ENT_QUOTES, $this->charset) . '
'; + } } $msg .= "\n\$_SERVER = " . var_export($_SERVER, true); error_log($msg); diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index 4f66e53..64f1d1b 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -39,21 +39,12 @@ class Exception extends \Exception implements Arrayable */ protected function toArrayRecursive($exception) { - if ($exception instanceof self) { - $array = array( - 'type' => get_class($this), - 'name' => $this->getName(), - 'message' => $this->getMessage(), - 'code' => $this->getCode(), - ); - } else { - $array = array( - 'type' => get_class($exception), - 'name' => 'Exception', - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } + $array = array( + 'type' => get_class($exception), + 'name' => $exception instanceof self ? $exception->getName() : 'Exception', + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + ); if (($prev = $exception->getPrevious()) !== null) { $array['previous'] = $this->toArrayRecursive($prev); } diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index 93b5a6b..c8c253a 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -422,6 +422,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Returns a value indicating whether the attribute is safe for massive assignments. * @param string $attribute attribute name * @return boolean whether the attribute is safe for massive assignments + * @see safeAttributes() */ public function isAttributeSafe($attribute) { @@ -429,6 +430,17 @@ class Model extends Component implements IteratorAggregate, ArrayAccess } /** + * Returns a value indicating whether the attribute is active in the current scenario. + * @param string $attribute attribute name + * @return boolean whether the attribute is active in the current scenario + * @see activeAttributes() + */ + public function isAttributeActive($attribute) + { + return in_array($attribute, $this->activeAttributes(), true); + } + + /** * Returns the text label for the specified attribute. * @param string $attribute the attribute name * @return string the attribute label diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index df0b2b2..f9cae98 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -96,7 +96,7 @@ class View extends Component /** * @var mixed custom parameters that are shared among view templates. */ - public $params; + public $params = array(); /** * @var array a list of available renderers indexed by their corresponding supported file extensions. * Each renderer may be a view renderer object or the configuration for creating the renderer object. @@ -142,11 +142,11 @@ class View extends Component */ public $dynamicPlaceholders = array(); /** - * @var array list of the registered asset bundles. The keys are the bundle names, and the values - * are booleans indicating whether the bundles have been registered. + * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values + * are the registered [[AssetBundle]] objects. * @see registerAssetBundle */ - public $assetBundles; + public $assetBundles = array(); /** * @var string the page title */ @@ -523,6 +523,9 @@ class View extends Component $this->trigger(self::EVENT_END_PAGE); $content = ob_get_clean(); + foreach(array_keys($this->assetBundles) as $bundle) { + $this->registerAssetFiles($bundle); + } echo strtr($content, array( self::PH_HEAD => $this->renderHeadHtml(), self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(), @@ -530,7 +533,6 @@ class View extends Component )); unset( - $this->assetBundles, $this->metaTags, $this->linkTags, $this->css, @@ -541,6 +543,24 @@ class View extends Component } /** + * Registers all files provided by an asset bundle including depending bundles files. + * Removes a bundle from [[assetBundles]] once registered. + * @param string $name name of the bundle to register + */ + private function registerAssetFiles($name) + { + if (!isset($this->assetBundles[$name])) { + return; + } + $bundle = $this->assetBundles[$name]; + foreach($bundle->depends as $dep) { + $this->registerAssetFiles($dep); + } + $bundle->registerAssets($this); + unset($this->assetBundles[$name]); + } + + /** * Marks the beginning of an HTML body section. */ public function beginBody() @@ -570,21 +590,44 @@ class View extends Component * Registers the named asset bundle. * All dependent asset bundles will be registered. * @param string $name the name of the asset bundle. + * @param integer|null $position if set, this forces a minimum position for javascript files. + * This will adjust depending assets javascript file position or fail if requirement can not be met. + * If this is null, asset bundles position settings will not be changed. + * See [[registerJsFile]] for more details on javascript position. * @return AssetBundle the registered asset bundle instance * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected */ - public function registerAssetBundle($name) + public function registerAssetBundle($name, $position = null) { if (!isset($this->assetBundles[$name])) { $am = $this->getAssetManager(); $bundle = $am->getBundle($name); $this->assetBundles[$name] = false; - $bundle->registerAssets($this); + // register dependencies + $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null; + foreach ($bundle->depends as $dep) { + $this->registerAssetBundle($dep, $pos); + } $this->assetBundles[$name] = $bundle; } elseif ($this->assetBundles[$name] === false) { throw new InvalidConfigException("A circular dependency is detected for bundle '$name'."); + } else { + $bundle = $this->assetBundles[$name]; + } + + if ($position !== null) { + $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null; + if ($pos === null) { + $bundle->jsOptions['position'] = $pos = $position; + } elseif ($pos > $position) { + throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'."); + } + // update position for all dependencies + foreach ($bundle->depends as $dep) { + $this->registerAssetBundle($dep, $pos); + } } - return $this->assetBundles[$name]; + return $bundle; } /** @@ -730,7 +773,7 @@ class View extends Component if (!empty($this->js[self::POS_HEAD])) { $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), array('type' => 'text/javascript')); } - return empty($lines) ? '' : implode("\n", $lines) . "\n"; + return empty($lines) ? '' : implode("\n", $lines); } /** @@ -747,7 +790,7 @@ class View extends Component if (!empty($this->js[self::POS_BEGIN])) { $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), array('type' => 'text/javascript')); } - return empty($lines) ? '' : implode("\n", $lines) . "\n"; + return empty($lines) ? '' : implode("\n", $lines); } /** @@ -768,6 +811,6 @@ class View extends Component $js = "jQuery(document).ready(function(){\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; $lines[] = Html::script($js, array('type' => 'text/javascript')); } - return empty($lines) ? '' : implode("\n", $lines) . "\n"; + return empty($lines) ? '' : implode("\n", $lines); } } diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index 5c778fc..7cb6451 100644 --- a/framework/yii/caching/RedisCache.php +++ b/framework/yii/caching/RedisCache.php @@ -10,7 +10,7 @@ namespace yii\caching; use yii\redis\Connection; /** - * RedisCache implements a cache application component based on [redis](http://redis.io/). + * RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6 or higher. * * RedisCache needs to be configured with [[hostname]], [[port]] and [[database]] of the server * to connect to. By default RedisCache assumes there is a redis server running on localhost at diff --git a/framework/yii/classes.php b/framework/yii/classes.php index d4f304c..78cce95 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -85,7 +85,7 @@ return array( 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php', 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', - 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', + 'yii\data\BaseDataProvider' => YII_PATH . '/data/BaseDataProvider.php', 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php', 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', @@ -118,6 +118,7 @@ return array( 'yii\db\pgsql\Schema' => YII_PATH . '/db/pgsql/Schema.php', 'yii\db\sqlite\QueryBuilder' => YII_PATH . '/db/sqlite/QueryBuilder.php', 'yii\db\sqlite\Schema' => YII_PATH . '/db/sqlite/Schema.php', + 'yii\grid\ActionColumn' => YII_PATH . '/grid/ActionColumn.php', 'yii\grid\CheckboxColumn' => YII_PATH . '/grid/CheckboxColumn.php', 'yii\grid\Column' => YII_PATH . '/grid/Column.php', 'yii\grid\DataColumn' => YII_PATH . '/grid/DataColumn.php', @@ -126,26 +127,26 @@ return array( 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php', - 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php', - 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php', - 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php', - 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php', - 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', - 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', - 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', - 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', - 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', - 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', + 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', + 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', + 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', + 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', + 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', + 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', + 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', + 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', + 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', + 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', @@ -194,6 +195,7 @@ return array( 'yii\web\Application' => YII_PATH . '/web/Application.php', 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php', 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', + 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php', 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php', 'yii\web\Controller' => YII_PATH . '/web/Controller.php', @@ -204,7 +206,6 @@ return array( 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php', 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php', - 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php', 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php', 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php', @@ -226,6 +227,7 @@ return array( 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php', 'yii\widgets\ActiveForm' => YII_PATH . '/widgets/ActiveForm.php', 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php', + 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php', 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php', 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php', @@ -235,7 +237,6 @@ return array( 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', - 'yii\widgets\ListViewBase' => YII_PATH . '/widgets/ListViewBase.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php index e2c771c..e9e3973 100644 --- a/framework/yii/console/controllers/MigrateController.php +++ b/framework/yii/console/controllers/MigrateController.php @@ -584,7 +584,7 @@ class MigrateController extends Controller ->from($this->migrationTable) ->orderBy('version DESC') ->limit($limit) - ->createCommand() + ->createCommand($this->db) ->queryAll(); $history = ArrayHelper::map($rows, 'version', 'apply_time'); unset($history[self::BASE_MIGRATION]); diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index 2fe0efb..c5f1bcd 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -48,16 +48,10 @@ use yii\db\Connection; * $posts = $provider->getModels(); * ~~~ * - * @property integer $count The number of data models in the current page. This property is read-only. - * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is - * uniquely identified by the corresponding key value in this array. This property is read-only. - * @property array $models The list of data models in the current page. This property is read-only. - * @property integer $totalCount Total number of possible data models. - * * @author Qiang Xue * @since 2.0 */ -class ActiveDataProvider extends DataProvider +class ActiveDataProvider extends BaseDataProvider { /** * @var Query the query that is used to fetch data models and [[totalCount]] @@ -82,10 +76,6 @@ class ActiveDataProvider extends DataProvider */ public $db; - private $_models; - private $_keys; - private $_totalCount; - /** * Initializes the DbCache component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. @@ -103,122 +93,72 @@ class ActiveDataProvider extends DataProvider } /** - * Returns the number of data models in the current page. - * This is equivalent to `count($provider->models)`. - * When [[pagination]] is false, this is the same as [[totalCount]]. - * @return integer the number of data models in the current page. - */ - public function getCount() - { - return count($this->getModels()); - } - - /** - * Returns the total number of data models. - * When [[pagination]] is false, this returns the same value as [[count]]. - * If [[totalCount]] is not explicitly set, it will be calculated - * using [[query]] with a COUNT query. - * @return integer total number of possible data models. - * @throws InvalidConfigException + * @inheritdoc */ - public function getTotalCount() + protected function prepareModels() { - if ($this->getPagination() === false) { - return $this->getCount(); - } elseif ($this->_totalCount === null) { - if (!$this->query instanceof Query) { - throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); - } - $query = clone $this->query; - $this->_totalCount = $query->limit(-1)->offset(-1)->count('*', $this->db); + if (!$this->query instanceof Query) { + throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } - return $this->_totalCount; - } - - /** - * Sets the total number of data models. - * @param integer $value the total number of data models. - */ - public function setTotalCount($value) - { - $this->_totalCount = $value; - } - - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - * @throws InvalidConfigException if [[query]] is not set or invalid. - */ - public function getModels() - { - if ($this->_models === null) { - if (!$this->query instanceof Query) { - throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); - } - if (($pagination = $this->getPagination()) !== false) { - $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); - } - if (($sort = $this->getSort()) !== false) { - $this->query->addOrderBy($sort->getOrders()); - } - $this->_models = $this->query->all($this->db); + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); + } + if (($sort = $this->getSort()) !== false) { + $this->query->addOrderBy($sort->getOrders()); } - return $this->_models; + return $this->query->all($this->db); } /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. + * @inheritdoc */ - public function getKeys() + protected function prepareKeys($models) { - if ($this->_keys === null) { - $this->_keys = array(); - $models = $this->getModels(); - if ($this->key !== null) { + $keys = array(); + if ($this->key !== null) { + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + return $keys; + } elseif ($this->query instanceof ActiveQuery) { + /** @var \yii\db\ActiveRecord $class */ + $class = $this->query->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + $pk = $pks[0]; foreach ($models as $model) { - if (is_string($this->key)) { - $this->_keys[] = $model[$this->key]; - } else { - $this->_keys[] = call_user_func($this->key, $model); - } + $keys[] = $model[$pk]; } - } elseif ($this->query instanceof ActiveQuery) { - /** @var \yii\db\ActiveRecord $class */ - $class = $this->query->modelClass; - $pks = $class::primaryKey(); - if (count($pks) === 1) { - $pk = $pks[0]; - foreach ($models as $model) { - $this->_keys[] = $model[$pk]; - } - } else { - foreach ($models as $model) { - $keys = array(); - foreach ($pks as $pk) { - $keys[] = $model[$pk]; - } - $this->_keys[] = json_encode($keys); + } else { + foreach ($models as $model) { + $kk = array(); + foreach ($pks as $pk) { + $kk[] = $model[$pk]; } + $keys[] = json_encode($kk); } - } else { - $this->_keys = array_keys($models); } + return $keys; + } else { + return array_keys($models); } - return $this->_keys; } /** - * Refreshes the data provider. - * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, - * they will re-execute the query and return the latest data available. + * @inheritdoc */ - public function refresh() + protected function prepareTotalCount() { - $this->_models = null; - $this->_totalCount = null; - $this->_keys = null; + if (!$this->query instanceof Query) { + throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); + } + $query = clone $this->query; + return $query->limit(-1)->offset(-1)->count('*', $this->db); } /** @@ -227,9 +167,7 @@ class ActiveDataProvider extends DataProvider public function setSort($value) { parent::setSort($value); - if (($sort = $this->getSort()) !== false && empty($sort->attributes) && - $this->query instanceof ActiveQuery) { - + if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQuery) { /** @var Model $model */ $model = new $this->query->modelClass; foreach($model->attributes() as $attribute) { diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index 9534803..77f31cd 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -47,15 +47,10 @@ use yii\helpers\ArrayHelper; * Note: if you want to use the sorting feature, you must configure the [[sort]] property * so that the provider knows which columns can be sorted. * - * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is - * uniquely identified by the corresponding key value in this array. - * @property array $models The list of data models in the current page. - * @property integer $totalCount Total number of possible data models. - * * @author Qiang Xue * @since 2.0 */ -class ArrayDataProvider extends DataProvider +class ArrayDataProvider extends BaseDataProvider { /** * @var string|callable the column that is used as the key of the data models. @@ -71,100 +66,54 @@ class ArrayDataProvider extends DataProvider */ public $allModels; - private $_totalCount; /** - * Returns the total number of data models. - * @return integer total number of possible data models. + * @inheritdoc */ - public function getTotalCount() + protected function prepareModels() { - if ($this->getPagination() === false) { - return $this->getCount(); - } elseif ($this->_totalCount === null) { - $this->_totalCount = count($this->allModels); + if (($models = $this->allModels) === null) { + return array(); } - return $this->_totalCount; - } - - /** - * Sets the total number of data models. - * @param integer $value the total number of data models. - */ - public function setTotalCount($value) - { - $this->_totalCount = $value; - } - - private $_models; - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - */ - public function getModels() - { - if ($this->_models === null) { - if (($models = $this->allModels) === null) { - return array(); - } - - if (($sort = $this->getSort()) !== false) { - $models = $this->sortModels($models, $sort); - } - - if (($pagination = $this->getPagination()) !== false) { - $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); - } + if (($sort = $this->getSort()) !== false) { + $models = $this->sortModels($models, $sort); + } - $this->_models = $models; + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } - return $this->_models; - } - /** - * Sets the data models in the current page. - * @param array $models the models in the current page - */ - public function setModels($models) - { - $this->_models = $models; + return $models; } - private $_keys; - /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. + * @inheritdoc */ - public function getKeys() + protected function prepareKeys($models) { - if ($this->_keys === null) { - $this->_keys = array(); - $models = $this->getModels(); - if ($this->key !== null) { - foreach ($models as $model) { - if (is_string($this->key)) { - $this->_keys[] = $model[$this->key]; - } else { - $this->_keys[] = call_user_func($this->key, $model); - } + if ($this->key !== null) { + $keys = array(); + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); } - } else { - $this->_keys = array_keys($models); } + return $keys; + } else { + return array_keys($models); } - return $this->_keys; } /** - * Sets the key values associated with the data models. - * @param array $keys the list of key values corresponding to [[models]]. + * @inheritdoc */ - public function setKeys($keys) + protected function prepareTotalCount() { - $this->_keys = $keys; + return count($this->allModels); } /** diff --git a/framework/yii/data/BaseDataProvider.php b/framework/yii/data/BaseDataProvider.php new file mode 100644 index 0000000..3b0669d --- /dev/null +++ b/framework/yii/data/BaseDataProvider.php @@ -0,0 +1,256 @@ + + * @since 2.0 + */ +abstract class BaseDataProvider extends Component implements DataProviderInterface +{ + /** + * @var string an ID that uniquely identifies the data provider among all data providers. + * You should set this property if the same page contains two or more different data providers. + * Otherwise, the [[pagination]] and [[sort]] mainly not work properly. + */ + public $id; + + private $_sort; + private $_pagination; + private $_keys; + private $_models; + private $_totalCount; + + + /** + * Prepares the data models that will be made available in the current page. + * @return array the available data models + */ + abstract protected function prepareModels(); + + /** + * Prepares the keys associated with the currently available data models. + * @param array $models the available data models + * @return array the keys + */ + abstract protected function prepareKeys($models); + + /** + * Returns a value indicating the total number of data models in this data provider. + * @return integer total number of data models in this data provider. + */ + abstract protected function prepareTotalCount(); + + /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false) + { + if ($forcePrepare || $this->_models === null) { + $this->_models = $this->prepareModels(); + } + if ($forcePrepare || $this->_keys === null) { + $this->_keys = $this->prepareKeys($this->_models); + } + } + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels() + { + $this->prepare(); + return $this->_models; + } + + /** + * Sets the data models in the current page. + * @param array $models the models in the current page + */ + public function setModels($models) + { + $this->_models = $models; + } + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys() + { + $this->prepare(); + return $this->_keys; + } + + /** + * Sets the key values associated with the data models. + * @param array $keys the list of key values corresponding to [[models]]. + */ + public function setKeys($keys) + { + $this->_keys = $keys; + } + + /** + * Returns the number of data models in the current page. + * @return integer the number of data models in the current page. + */ + public function getCount() + { + return count($this->getModels()); + } + + /** + * Returns the total number of data models. + * When [[pagination]] is false, this returns the same value as [[count]]. + * Otherwise, it will call [[prepareTotalCount()]] to get the count. + * @return integer total number of possible data models. + */ + public function getTotalCount() + { + if ($this->getPagination() === false) { + return $this->getCount(); + } elseif ($this->_totalCount === null) { + $this->_totalCount = $this->prepareTotalCount(); + } + return $this->_totalCount; + } + + /** + * Sets the total number of data models. + * @param integer $value the total number of data models. + */ + public function setTotalCount($value) + { + $this->_totalCount = $value; + } + + /** + * Returns the pagination object used by this data provider. + * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values + * of [[Pagination::totalCount]] and [[Pagination::pageCount]]. + * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination() + { + if ($this->_pagination === null) { + $this->_pagination = new Pagination; + if ($this->id !== null) { + $this->_pagination->pageVar = $this->id . '-page'; + } + } + return $this->_pagination; + } + + /** + * Sets the pagination for this data provider. + * @param array|Pagination|boolean $value the pagination to be used by this data provider. + * This can be one of the following: + * + * - a configuration array for creating the pagination object. The "class" element defaults + * to 'yii\data\Pagination' + * - an instance of [[Pagination]] or its subclass + * - false, if pagination needs to be disabled. + * + * @throws InvalidParamException + */ + public function setPagination($value) + { + if (is_array($value)) { + $config = array( + 'class' => Pagination::className(), + ); + if ($this->id !== null) { + $config['pageVar'] = $this->id . '-page'; + } + $this->_pagination = Yii::createObject(array_merge($config, $value)); + } elseif ($value instanceof Pagination || $value === false) { + $this->_pagination = $value; + } else { + throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.'); + } + } + + /** + * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort() + { + if ($this->_sort === null) { + $this->setSort(array()); + } + return $this->_sort; + } + + /** + * Sets the sort definition for this data provider. + * @param array|Sort|boolean $value the sort definition to be used by this data provider. + * This can be one of the following: + * + * - a configuration array for creating the sort definition object. The "class" element defaults + * to 'yii\data\Sort' + * - an instance of [[Sort]] or its subclass + * - false, if sorting needs to be disabled. + * + * @throws InvalidParamException + */ + public function setSort($value) + { + if (is_array($value)) { + $config = array( + 'class' => Sort::className(), + ); + if ($this->id !== null) { + $config['sortVar'] = $this->id . '-sort'; + } + $this->_sort = Yii::createObject(array_merge($config, $value)); + } elseif ($value instanceof Sort || $value === false) { + $this->_sort = $value; + } else { + throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.'); + } + } + + /** + * Refreshes the data provider. + * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, + * they will re-execute the query and return the latest data available. + */ + public function refresh() + { + $this->_totalCount = null; + $this->_models = null; + $this->_keys = null; + } +} diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php deleted file mode 100644 index d75be6f..0000000 --- a/framework/yii/data/DataProvider.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @since 2.0 - */ -abstract class DataProvider extends Component implements DataProviderInterface -{ - /** - * @var string an ID that uniquely identifies the data provider among all data providers. - * You should set this property if the same page contains two or more different data providers. - * Otherwise, the [[pagination]] and [[sort]] mainly not work properly. - */ - public $id; - - private $_sort; - private $_pagination; - - /** - * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. - */ - public function getPagination() - { - if ($this->_pagination === null) { - $this->_pagination = new Pagination; - if ($this->id !== null) { - $this->_pagination->pageVar = $this->id . '-page'; - } - $this->_pagination->totalCount = $this->getTotalCount(); - } - return $this->_pagination; - } - - /** - * Sets the pagination for this data provider. - * @param array|Pagination|boolean $value the pagination to be used by this data provider. - * This can be one of the following: - * - * - a configuration array for creating the pagination object. The "class" element defaults - * to 'yii\data\Pagination' - * - an instance of [[Pagination]] or its subclass - * - false, if pagination needs to be disabled. - * - * @throws InvalidParamException - */ - public function setPagination($value) - { - if (is_array($value)) { - $config = array( - 'class' => Pagination::className(), - ); - if ($this->id !== null) { - $config['pageVar'] = $this->id . '-page'; - } - $this->_pagination = Yii::createObject(array_merge($config, $value)); - } elseif ($value instanceof Pagination || $value === false) { - $this->_pagination = $value; - } else { - throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.'); - } - } - - /** - * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled. - */ - public function getSort() - { - if ($this->_sort === null) { - $this->setSort(array()); - } - return $this->_sort; - } - - /** - * Sets the sort definition for this data provider. - * @param array|Sort|boolean $value the sort definition to be used by this data provider. - * This can be one of the following: - * - * - a configuration array for creating the sort definition object. The "class" element defaults - * to 'yii\data\Sort' - * - an instance of [[Sort]] or its subclass - * - false, if sorting needs to be disabled. - * - * @throws InvalidParamException - */ - public function setSort($value) - { - if (is_array($value)) { - $config = array( - 'class' => Sort::className(), - ); - if ($this->id !== null) { - $config['sortVar'] = $this->id . '-sort'; - } - $this->_sort = Yii::createObject(array_merge($config, $value)); - } elseif ($value instanceof Sort || $value === false) { - $this->_sort = $value; - } else { - throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.'); - } - } - - /** - * Returns the number of data models in the current page. - * @return integer the number of data models in the current page. - */ - public function getCount() - { - return count($this->getModels()); - } -} diff --git a/framework/yii/data/DataProviderInterface.php b/framework/yii/data/DataProviderInterface.php index f0bc39d..1dea1e6 100644 --- a/framework/yii/data/DataProviderInterface.php +++ b/framework/yii/data/DataProviderInterface.php @@ -19,6 +19,18 @@ namespace yii\data; interface DataProviderInterface { /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false); + + /** * Returns the number of data models in the current page. * This is equivalent to `count($provider->getModels())`. * When [[pagination]] is false, this is the same as [[totalCount]]. diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php index 12997ee..375e91f 100644 --- a/framework/yii/db/ActiveQuery.php +++ b/framework/yii/db/ActiveQuery.php @@ -55,12 +55,6 @@ class ActiveQuery extends Query */ public $with; /** - * @var string|callable $column the name of the column by which the query results should be indexed by. - * This can also be a callable (e.g. anonymous function) that returns the index value based on the given - * row or model data. For more details, see [[indexBy()]]. - */ - public $indexBy; - /** * @var boolean whether to return each record as an array. If false (default), an object * of [[modelClass]] will be created to represent each record. */ @@ -174,7 +168,7 @@ class ActiveQuery extends Query /** * Sets the [[asArray]] property. * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. - * @return ActiveQuery the query object itself + * @return static the query object itself */ public function asArray($value = true) { @@ -202,7 +196,7 @@ class ActiveQuery extends Query * ))->all(); * ~~~ * - * @return ActiveQuery the query object itself + * @return static the query object itself */ public function with() { @@ -229,12 +223,11 @@ class ActiveQuery extends Query * } * ~~~ * - * @return ActiveQuery the query object itself + * @return static the query object itself */ public function indexBy($column) { - $this->indexBy = $column; - return $this; + return parent::indexBy($column); } private function createModels($rows) diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php index f05c56a..c1c8b2d 100644 --- a/framework/yii/db/ActiveRelation.php +++ b/framework/yii/db/ActiveRelation.php @@ -66,7 +66,7 @@ class ActiveRelation extends ActiveQuery * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return ActiveRelation the relation object itself. + * @return static the relation object itself. */ public function via($relationName, $callable = null) { @@ -86,7 +86,7 @@ class ActiveRelation extends ActiveQuery * in the [[primaryModel]] table. * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return ActiveRelation + * @return static */ public function viaTable($tableName, $link, $callable = null) { diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index bfb8a26..bd18e1f 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -88,7 +88,7 @@ class Command extends \yii\base\Component * Specifies the SQL statement to be executed. * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well. * @param string $sql the SQL statement to be set. - * @return Command this command instance + * @return static this command instance */ public function setSql($sql) { @@ -174,15 +174,16 @@ class Command extends \yii\base\Component * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. * @param integer $length length of the data type * @param mixed $driverOptions the driver-specific options - * @return Command the current command being executed + * @return static the current command being executed * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php */ public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindParam($name, $value, $this->getPdoType($value)); - } elseif ($length === null) { + $dataType = $this->db->getSchema()->getPdoType($value); + } + if ($length === null) { $this->pdoStatement->bindParam($name, $value, $dataType); } elseif ($driverOptions === null) { $this->pdoStatement->bindParam($name, $value, $dataType, $length); @@ -201,17 +202,16 @@ class Command extends \yii\base\Component * placeholders, this will be the 1-indexed position of the parameter. * @param mixed $value The value to bind to the parameter * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @return Command the current command being executed + * @return static the current command being executed * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php */ public function bindValue($name, $value, $dataType = null) { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindValue($name, $value, $this->getPdoType($value)); - } else { - $this->pdoStatement->bindValue($name, $value, $dataType); + $dataType = $this->db->getSchema()->getPdoType($value); } + $this->pdoStatement->bindValue($name, $value, $dataType); $this->_params[$name] = $value; return $this; } @@ -225,7 +225,7 @@ class Command extends \yii\base\Component * e.g. `array(':name' => 'John', ':age' => 25)`. By default, the PDO type of each value is determined * by its PHP type. You may explicitly specify the PDO type by using an array: `array(value, type)`, * e.g. `array(':name' => 'John', ':profile' => array($profile, \PDO::PARAM_LOB))`. - * @return Command the current command being executed + * @return static the current command being executed */ public function bindValues($values) { @@ -236,7 +236,7 @@ class Command extends \yii\base\Component $type = $value[1]; $value = $value[0]; } else { - $type = $this->getPdoType($value); + $type = $this->db->getSchema()->getPdoType($value); } $this->pdoStatement->bindValue($name, $value, $type); $this->_params[$name] = $value; @@ -246,25 +246,6 @@ class Command extends \yii\base\Component } /** - * Determines the PDO type for the given PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - private function getPdoType($data) - { - static $typeMap = array( // php type => PDO type - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ); - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } - - /** * Executes the SQL statement. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. * No result set will be returned. diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php index d1e7864..dbe0424 100644 --- a/framework/yii/db/Query.php +++ b/framework/yii/db/Query.php @@ -166,7 +166,7 @@ class Query extends Component * } * ~~~ * - * @return Query the query object itself + * @return static the query object itself */ public function indexBy($column) { @@ -325,7 +325,7 @@ class Query extends Component * (which means the column contains a DB expression). * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. - * @return Query the query object itself + * @return static the query object itself */ public function select($columns, $option = null) { @@ -340,7 +340,7 @@ class Query extends Component /** * Sets the value indicating whether to SELECT DISTINCT or not. * @param bool $value whether to SELECT DISTINCT or not. - * @return Query the query object itself + * @return static the query object itself */ public function distinct($value = true) { @@ -355,7 +355,7 @@ class Query extends Component * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`). * The method will automatically quote the table names unless it contains some parenthesis * (which means the table is given as a sub-query or DB expression). - * @return Query the query object itself + * @return static the query object itself */ public function from($tables) { @@ -431,7 +431,7 @@ class Query extends Component * * @param string|array $condition the conditions that should be put in the WHERE part. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see andWhere() * @see orWhere() */ @@ -448,7 +448,7 @@ class Query extends Component * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see where() * @see orWhere() */ @@ -469,7 +469,7 @@ class Query extends Component * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see where() * @see andWhere() */ @@ -560,7 +560,7 @@ class Query extends Component * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return Query the query object itself + * @return static the query object itself * @see addGroupBy() */ public function groupBy($columns) @@ -578,7 +578,7 @@ class Query extends Component * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return Query the query object itself + * @return static the query object itself * @see groupBy() */ public function addGroupBy($columns) @@ -599,7 +599,7 @@ class Query extends Component * @param string|array $condition the conditions to be put after HAVING. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see andHaving() * @see orHaving() */ @@ -616,7 +616,7 @@ class Query extends Component * @param string|array $condition the new HAVING condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see having() * @see orHaving() */ @@ -637,7 +637,7 @@ class Query extends Component * @param string|array $condition the new HAVING condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see having() * @see andHaving() */ @@ -659,7 +659,7 @@ class Query extends Component * (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return Query the query object itself + * @return static the query object itself * @see addOrderBy() */ public function orderBy($columns) @@ -675,7 +675,7 @@ class Query extends Component * (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). - * @return Query the query object itself + * @return static the query object itself * @see orderBy() */ public function addOrderBy($columns) @@ -710,7 +710,7 @@ class Query extends Component /** * Sets the LIMIT part of the query. * @param integer $limit the limit. Use null or negative value to disable limit. - * @return Query the query object itself + * @return static the query object itself */ public function limit($limit) { @@ -721,7 +721,7 @@ class Query extends Component /** * Sets the OFFSET part of the query. * @param integer $offset the offset. Use null or negative value to disable offset. - * @return Query the query object itself + * @return static the query object itself */ public function offset($offset) { @@ -732,7 +732,7 @@ class Query extends Component /** * Appends a SQL statement using UNION operator. * @param string|Query $sql the SQL statement to be appended using UNION - * @return Query the query object itself + * @return static the query object itself */ public function union($sql) { @@ -744,7 +744,7 @@ class Query extends Component * Sets the parameters to be bound to the query. * @param array $params list of query parameter values indexed by parameter placeholders. * For example, `array(':name' => 'Dan', ':age' => 31)`. - * @return Query the query object itself + * @return static the query object itself * @see addParams() */ public function params($params) @@ -757,7 +757,7 @@ class Query extends Component * Adds additional parameters to be bound to the query. * @param array $params list of query parameter values indexed by parameter placeholders. * For example, `array(':name' => 'Dan', ':age' => 31)`. - * @return Query the query object itself + * @return static the query object itself * @see params() */ public function addParams($params) diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index 1d86616..e32917f 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -187,6 +187,26 @@ abstract class Schema extends Object } /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = array( + // php type => PDO type + 'boolean' => \PDO::PARAM_BOOL, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ); + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** * Refreshes the schema. * This method cleans up all cached table schemas so that they can be re-created later * to reflect the database schema change. diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index ba7fcae..a789694 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -101,7 +101,8 @@ class Schema extends \yii\db\Schema $this->db->open(); // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658 - if (version_compare($this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION), '9.1.0', '<=')) { + $version = $this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION); + if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) { return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; } else { return $this->db->pdo->quote($str); @@ -237,4 +238,24 @@ class Schema extends \yii\db\Schema } return $tableNames; } + + /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = array( + // php type => PDO type + 'boolean' => \PDO::PARAM_INT, // PARAM_BOOL is not supported by CUBRID PDO + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ); + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } } diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php index dd027a7..9550b57 100644 --- a/framework/yii/debug/Module.php +++ b/framework/yii/debug/Module.php @@ -8,6 +8,7 @@ namespace yii\debug; use Yii; +use yii\base\Application; use yii\base\View; use yii\web\HttpException; @@ -55,7 +56,11 @@ class Module extends \yii\base\Module parent::init(); $this->dataPath = Yii::getAlias($this->dataPath); $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this); - Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar')); + // do not initialize view component before application is ready (needed when debug in preload) + $module = $this; + Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() use ($module) { + Yii::$app->getView()->on(View::EVENT_END_BODY, array($module, 'renderToolbar')); + }); foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) { $config['module'] = $this; diff --git a/framework/yii/gii/components/ActiveField.php b/framework/yii/gii/components/ActiveField.php index 6a67217..8bb67a9 100644 --- a/framework/yii/gii/components/ActiveField.php +++ b/framework/yii/gii/components/ActiveField.php @@ -32,6 +32,10 @@ class ActiveField extends \yii\widgets\ActiveField } } + /** + * Makes filed remember its value between page reloads + * @return static the field object itself + */ public function sticky() { $this->options['class'] .= ' sticky'; diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php index a649589..13e2b82 100644 --- a/framework/yii/gii/generators/crud/templates/views/_search.php +++ b/framework/yii/gii/generators/crud/templates/views/_search.php @@ -23,7 +23,10 @@ use yii\widgets\ActiveForm;