diff --git a/.travis.yml b/.travis.yml index 41c17082c5..ab096c12d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ php: services: - redis-server + - memcached before_script: - ./tests/travis/mysql-setup.sh diff --git a/CHANGELOG b/CHANGELOG index 25e769c6a2..a0a9d5e02d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,78 @@ Yii Framework Change Log ======================== +Work in progress +---------------- -Version 1.1.14 --------------- +Version 1.1.15 under development +-------------------------------- +- Bug #268: Fixed Active Record count error when some field name starting from 'count' (nineinchnick) +- Bug #788: createIndex is not using the recommended way to create unique indexes on Postgres (nineinchnick) +- Bug #2235: CPgsqlColumnSchema can't parse default value for numeric field (cebe, pavimus) +- Bug #2378: CActiveRecord::tableName() in namespaced model returned fully qualified class name (velosipedist, cebe) +- Bug #2654: Allow CDbCommand to compose queries without 'from' clause (klimov-paul) +- Bug #2658: CBaseListView, CGridView, CListView: added note about $itemsCssClass and $pagerCssClass properties, they must not contain empty string, null or false values (resurtm) +- Bug #2969: CPgsqlSchema::addColumn() converts column type twice (cebe, klimov-paul) +- Bug #2753: Fixed CErrorHandler::errorAction ignored if error occurs while AJAX request (klimov-paul) +- Bug #2756: Fixed applying condition twice during Active Record relation lazy loading (klimov-paul) +- Bug #2770: Fixed CClientScript renders scripts with different HTML options inside same tag (klimov-paul) +- Bug #2778: Fixed throwing unnecessary exception in CFileValidator when validating MIME types for a file upload that failed (Rupert-RR) +- Bug #2785: Use table name with schema in composeMultipleInsertCommand (nineinchnick) +- Bug #2836: Fixed rendering when try-catching widget Exception while 'captureOutput' is set to true (darkheir) +- Bug #2855: Fixed issue with Component::__call() and normal properties holding a Closure (cebe) +- Bug #2862: Fixed array_merge caused renumbering of $data indexes in CHtml::radioButtonList() (ligser) +- Bug #2864: Fixed CGridView ajax calls failing CSRF validation when ajaxType is set to POST (nineinchnick) +- Bug #2874: Fixed duplicate columns selection for HAS_MANY relation with composite primary key (borro) +- Bug #2876: Fixed single quotes in comments column causes syntax error in model code generated by Gii(klimov-paul) +- Bug #2884: Fixed problem with table alias in CActiveRecord that has been introduced in 1.1.14 (cebe) +- Bug #2887: Fixed CFormElement is missing __isset() (bijibox) +- Bug #2912: Add options parameter to CListView beforeAjaxUpdate (spikyjt) +- Bug #2944: Fixed CDbCriteria fails to merge limit when it is 0 (softark) +- Bug #2959: Fixed CFileValidator to encode file name, while composing error messages (klimov-paul) +- Bug #2963: CAssetManager::generatePath no longer uses basename for hasing (eirikhm) +- Bug #2970: Fixed Active Record may join same relation twice on eager loading. (klimov-paul) +- Bug #3010: Problem with callables given as values to CDetailView. CDetailView now only allows annonymous functions to be called, all other values will be taken as value (cebe) +- Bug #3064: Fixed problem with array to string converion in CDbMigration methods that accept array parameters (cebe) +- Bug #3113: Fixed problems with realpath(false) which can occur in combination with Yii::getPathOfAlias() when alias does not exist (cebe) +- Bug #3134: Fixed the issue that query cache returns the same data for the same SQL but different query methods (qiangxue) +- Bug #3144: It wasn't possible to use attributes with spaces in validation rules (samdark) +- Bug: Fixed the bug that backslashes are not escaped by CDbCommandBuilder::buildSearchCondition() (qiangxue) +- Bug: Fixed URL parsing so it's now properly giving 404 for URLs like "http://example.com//////site/about/////" (samdark) +- Bug: Fixed an issue with CFilehelper and not accessable directories which resulted in endless loop (cebe) +- Enh: Public method CFileHelper::createDirectory() has been added (klimov-paul) +- Enh #106: Added getters to CGridColumn to allow getting cell contents for extended use cases of CGridView (cebe) +- Enh #182: CSort: allow arrays in asc/desc keys of virtual attributes (nineinchnick) +- Enh #640: Introduce bigpk and bigint column types in each class extending CDbSchema (nineinchnick) +- Enh #873: CStatRelation (CActiveRecord::STAT) now supports scopes (resurtm, klimov-paul) +- Enh #1515: Post-JOIN operations (use|force|ignore index()) support in relational queries (KonovalovMaxim, resurtm) +- Enh #1593: Allow access to exception currently processed by CErrorHandler (klimov-paul) +- Enh #2540: Enable CJSON to use JsonSerializable interface when serializing objects (sammousa) +- Enh #2664: Added support for HTTP PATCH requests to CHttpRequest (cebe) +- Enh #2688: CHtml::beginForm() now supports additional HTTP methods, via a hidden `_method` field. (phpnode) +- Enh #2722: CFileHelper::findFiles() accepts absolutePaths in $options and returns absolute paths if true or relative ones otherwise (defaults to true) (pavel-voronin) +- Enh #2737: CFileCache: added cachePathMode and cacheFileMode options to set modes used by chmod() for cache directory and files (ujovlado) +- Enh #2758: Updated phpdoc in blog demo to match current IDE supported syntax (samdark) +- Enh #2777: Allow Yii::import() and Yii::createComponent() to import classes that are loaded by other autoloaders e.g. composer (cebe) +- Enh #2791: requirements/index.php: added CRYPT_BLOWFISH check for CPasswordHelper (tom--) +- Enh #2799: Add HTML5 input support for color, datetime, datetime-local, week and search to CHtml and CActiveForm (phpnode) +- Enh #2817: Allow specifying $colums and $refColumns arguments as array in various CDbSchema methods (mynameiszanders, samdark) +- Enh #2852: Refactored ShellCommand to be easier to extend (samdark, mindplay-dk) +- Enh #2908: Add insertMultiple to Migrations (luislobo) +- Enh #3014: Allow changing the database used by ActiveRecord in beforeCount() like it is possible in beforeFind() already (cebe) +- Enh #3023: Added support for formatting DateTime instances to CFormatter (cebe, nitso) +- Enh #3027: Added custom encodeLabel attributes of the CMenu items (hugeval) +- Enh #3048: CApcCache is now compatible with APCu (iobotis, samdark) +- Enh #3068: Added CDbCommand::naturalLeftJoin() and CDbCommand::naturalRightJoin() (bunchachis) +- Enh #3115: Updated phpdoc for better code completion in modern IDEs (samdark) +- Enh #3147: Updated Request::getIsSecureConnection() to work with lower and uppercase config values (cebe) +- Enh #3182: Added namespace support for controllers in subdirectories (Ekstazi, samdark) +- Chg #3137: Upgraded HTMLPurifier to 4.6.0 (samdark) +- New #2955: Added official support for MariaDB (cebe, DaSourcerer) + +Version 1.1.14 August 11, 2013 +------------------------------ - Bug: There was unnecessary echo in CRUD views generated by Gii (samdark) - Bug: CJavaScript::encode was formatting floats in a wrong way during encoding (samdark) +- Bug: Fixed minLength and maxLength range check in CCaptchaAction::generateVerifyCode so values are now always stay in bounds (samdark) - Bug #101: CActiveFinder::buildJoinTree() no longer uses 'false' for 'select' value (klimov-paul) - Bug #135: Fixed wrong CActiveRecord rows count with having (klimov-paul) - Bug #139: Fixed Active Record lazy load through relation with condition (klimov-paul) @@ -83,8 +151,9 @@ Version 1.1.14 - Bug #2565: CCaptchaAction in ImageMagick mode used to issue an exception in case $backColor or $foreColor have had leading zeros (resurtm) - Bug #2581: Fixed the bug with empty ajaxVar in jquery.yiilistview.js and jquery.yiigridview.js (seregagl) - Bug #2602: CUrlValidator and CEmailValidator now works correctly with display_errors = on and validateIDN = true (creocoder) +- Bug #2662: CLocale::getTerritory() used to return null value even for proper input values, bug fix #1622 made in 1.1.13 has been reverted (resurtm) - Bug #2632: Fixed inability import non-build aliases by config on some case (Yiivgeny) -- Bug #2651: CHttpSession wasn't using GC probability/divisor from php.ini (marcovtwout, cebe, samdark) +- Bug #2651: CHttpSession was using hardcoded GC probability/divisor values (marcovtwout, cebe, samdark) - Enh: Better CFileLogRoute performance (Qiang, samdark) - Enh: Refactored CHttpRequest::getDelete and CHttpRequest::getPut not to use _restParams directly (samdark) - Enh #100: CLogFilter::$logVars can now be array of arrays intended for designating particular items of the $GLOBALS (resurtm, tomtomsen) @@ -131,6 +200,7 @@ Version 1.1.14 - Enh #2494: Allow to configure CBaseListView emptyText container tag name (ifdattic) - Enh #2529: Silenced all chmod calls to prevent "chmod() operation not allowed" error on NTFS (samdark) - Enh #2602: CEmailValidator and CUrlValidator now uses native PHP `idn` extension in case it is available (`idn_to_ascii` and `idn_ to_ utf8` functions) and Net_IDNA2 otherwise (resurtm, creocoder) +- Enh #2642: Support third party autoloaders when importing classes via Yii::import() (phpnode) - Chg: Upgraded HTMLPurifier to v4.5.0 (samdark) - Chg #645: CDbConnection now throws CDbException when failed to open DB connection instead of failing with a warning (kidol, eirikhm, samdark, cebe) - Chg #895: Add second argument $params to client validation function (slavcodev) @@ -140,7 +210,7 @@ Version 1.1.14 - Chg #2187: Vendors: punycode.js updated from 1.1.1 (June 27, 2012) to 1.2.0 (October 10, 2012) (resurtm) - Chg #2461: Upgraded jquery star rating to 4.11 (samdark) - Chg #2531: Upgraded jquery masked input to 1.3.1 (samdark) -- New: Added CRedisCache which uses redis key value store as cache backend (cebe) +- New: Added CRedisCache which uses redis key value store as cache backend (cebe, maxlun86) - New #575: Yii registering at Packagist, added composer info file (schmunk42) - New #1785: Added CPasswordHelper (tom--) - New #2178: Added Catalan Translation (ArnauAregall) diff --git a/UPGRADE b/UPGRADE index dfe19603aa..66be63eaff 100644 --- a/UPGRADE +++ b/UPGRADE @@ -1,4 +1,4 @@ - Upgrading Instructions for Yii Framework v1.1.14 + Upgrading Instructions for Yii Framework v1.1.15 ================================================ !!!IMPORTANT!!! @@ -17,6 +17,16 @@ General upgrade instructions - Check if everything is OK, if not — revert from backup and post issues to Yii issue tracker. +Upgrading from v1.1.14 +---------------------- + +- CErrorHandler now runs errorAction for errors, which appear via AJAX request. + If you use CErrorHandler::errorAction, make sure it handles AJAX request properly. + +- The possibility to use callables for values of CDetailView introduced a problem with string beeing interpreted as + PHP functions. CDetailView now only allows anonymous functions to be called, all other values will be taken as value. + + Upgrading from v1.1.13 ---------------------- diff --git a/build/commands/api/ApiModel.php b/build/commands/api/ApiModel.php index 8c9a5ebdbd..edfd9b0bfd 100644 --- a/build/commands/api/ApiModel.php +++ b/build/commands/api/ApiModel.php @@ -202,7 +202,7 @@ protected function processLink($matches) if(($text=trim($matches[2]))==='') $text=$url; - if(preg_match('/^(http|ftp):\/\//i',$url)) // an external URL + if(preg_match('/^(http|https|ftp):\/\//i',$url)) // an external URL return "$text"; $url=$this->resolveInternalUrl($url); return $url===''?$text:'{{'.$url.'|'.$text.'}}'; diff --git a/demos/blog/protected/models/Comment.php b/demos/blog/protected/models/Comment.php index eafa378e7f..43c2117a61 100644 --- a/demos/blog/protected/models/Comment.php +++ b/demos/blog/protected/models/Comment.php @@ -1,24 +1,23 @@ password)===$this->password; + return CPasswordHelper::verifyPassword($password,$this->password); } public function hashPassword($password) { - return crypt($password, $this->generateSalt()); + return CPasswordHelper::hashPassword($password); } } ~~~ diff --git a/docs/blog/ru/comment.admin.txt b/docs/blog/ru/comment.admin.txt index aa28e157ff..54722220a2 100644 --- a/docs/blog/ru/comment.admin.txt +++ b/docs/blog/ru/comment.admin.txt @@ -49,7 +49,7 @@ public function approve() } ~~~ -Здесь мы просто выставляем свойство `status` комментария в `approved` согласно значению соответсвующей константы класса `Comment`: +Здесь мы просто выставляем свойство `status` комментария в `approved` согласно значению соответствующей константы класса `Comment`: ~~~ [php] diff --git a/docs/blog/ru/final.future.txt b/docs/blog/ru/final.future.txt index 1966b4b192..fdabcf25b5 100644 --- a/docs/blog/ru/final.future.txt +++ b/docs/blog/ru/final.future.txt @@ -48,7 +48,7 @@ return array( ------------------------------------------------ Хотя сам Yii [довольно быстр](http://www.yiiframework.com/performance/), не обязательно, -что приложение, написанное с использованием Yii, будет работать также эффективно. Есть +что приложение, написанное с использованием Yii, будет работать так же эффективно. Есть несколько мест в приложении, где мы можем улучшить работу. Например, портлет облака тэгов может быть одним из узких мест в работе, потому что он использует сложный запрос к базе данных и логику PHP. diff --git a/docs/blog/ru/post.admin.txt b/docs/blog/ru/post.admin.txt index 30a22a670d..4ccfbac95c 100644 --- a/docs/blog/ru/post.admin.txt +++ b/docs/blog/ru/post.admin.txt @@ -3,7 +3,7 @@ Под управлением записями подразумевается отображение их списка в административном разделе с возможностью просматривать записи с любым статусом, редактировать и -удалять их. Этот функционал реализуется в действиях `admin` и `delete` +удалять их. Эта функциональность реализуется в действиях `admin` и `delete` соответственно. Код, сгенерированный при помощи `Gii` почти не нуждается в изменениях. Ниже мы объясним, как реализованы эти действия. @@ -28,8 +28,8 @@ public function actionAdmin() } ~~~ -Данный код полностью сгенерирован `Gii`. Сначала создаётся модель `Post` со сценарием -`search` [scenario](/doc/guide/ru/form.model), которую мы будем использовать для +Данный код полностью сгенерирован `Gii`. Сначала создаётся модель `Post` со [сценарием](/doc/guide/ru/form.model) +`search`, которую мы будем использовать для сбора критериев поиска, указанных пользователем. Далее мы присваиваем данные, введённые пользователем, модели. И, наконец, мы выводим отображение `admin`, используя модель. diff --git a/docs/blog/ru/prototype.auth.txt b/docs/blog/ru/prototype.auth.txt index 5f73cbe83b..98ac04b596 100644 --- a/docs/blog/ru/prototype.auth.txt +++ b/docs/blog/ru/prototype.auth.txt @@ -67,12 +67,10 @@ class UserIdentity extends CUserIdentity Для того, чтобы проверить, ввёл ли пользователь правильный пароль, мы вызываем метод `validatePassword` класса `User`. Нам необходимо изменить файл `/wwwroot/blog/protected/models/User.php` как показано ниже. Отметим, что -вместо хранения пароля в БД в явном виде, мы используем хеш от пароля и генерируемого -случайным образом ключа (соли). При проверке введённого пользователем пароля, -вместо сравнения паролей, мы должны сравнивать хеши. Мы используем стандартную -PHP-функцию `crypt()` для хэширования и проверки пароля. Ознакомьтесь с вики-статьей -[Use crypt() for password storage](http://www.yiiframework.com/wiki/425), которая -описывает все подробности работы с данной функцией. +вместо хранения пароля в БД в явном виде, мы сохраняем его хеш. При проверке введённого пользователем пароля, +вместо сравнения паролей, мы должны сравнивать хеши. Для хеширования пароля и его проверки мы используем входящий +в Yii класс [CPasswordHelper]. + ~~~ [php] @@ -81,12 +79,12 @@ class User extends CActiveRecord ...... public function validatePassword($password) { - return crypt($password,$this->password)===$this->password; + return CPasswordHelper::verifyPassword($password,$this->password); } public function hashPassword($password) { - return crypt($password, $this->generateSalt()); + return CPasswordHelper::hashPassword($password); } } ~~~ diff --git a/docs/blog/uk/post.admin.txt b/docs/blog/uk/post.admin.txt index 7dbd8e481e..2a0c0b1595 100644 --- a/docs/blog/uk/post.admin.txt +++ b/docs/blog/uk/post.admin.txt @@ -3,7 +3,7 @@ Під управлінням записами мається на увазі відображення їх списку у адміністративному розділі з можливістю переглядати записи з будь-яким статусом, редагувати та -видаляти їх. Цей функціонал реалізується у діях `admin` та `delete` +видаляти їх. Ця функціональність реалізується у діях `admin` та `delete` відповідно. Код, згенерований за допомогою `Gii`, майже не потребує змін. Нижче ми пояснимо, як реалізовані ці дії. diff --git a/docs/blog/uk/prototype.auth.txt b/docs/blog/uk/prototype.auth.txt index c7d8d4c0fe..8707af2205 100644 --- a/docs/blog/uk/prototype.auth.txt +++ b/docs/blog/uk/prototype.auth.txt @@ -64,12 +64,9 @@ class UserIdentity extends CUserIdentity Для того, щоб перевірити чи ввів користувач правильний пароль, ми викликаємо метод `validatePassword` класу `User`. Нам необхідно змінити файл `/wwwroot/blog/protected/models/User.php` як показано нижче. -Відзначимо, що замість зберігання пароля у БД у явному вигляді, -ми використовуємо хеш від пароля та ключа (солі), що генерується випадковим чином. -При перевірці введеного користувачем пароля, замість порівняння паролів, ми повинні порівнювати хеші. -Ми використовуємо стандартну PHP-функцію `crypt()` для хешування і перевірки пароля. -Ознайомтеся з вікі-статтею [Use crypt() for password storage](http://www.yiiframework.com/wiki/425), -яка описує всі подробиці роботи з даною функцією. +Відзначимо, що замість зберігання пароля у БД у явному вигляді, ми зберігаємо його хеш. +При перевірці введеного користувачем пароля, замість порівняння паролів, ми повинні порівнювати хеші. +Для хешування пароля і його перевірки ми використовуємо клас [CPasswordHelper], що входить до Yii. ~~~ [php] @@ -78,12 +75,12 @@ class User extends CActiveRecord ...... public function validatePassword($password) { - return crypt($password,$this->password)===$this->password; + return CPasswordHelper::verifyPassword($password,$this->password); } public function hashPassword($password) { - return crypt($password, $this->generateSalt()); + return CPasswordHelper::hashPassword($password); } } ~~~ diff --git a/docs/blog/uk/prototype.database.txt b/docs/blog/uk/prototype.database.txt index 5bd30e997f..874cae166e 100644 --- a/docs/blog/uk/prototype.database.txt +++ b/docs/blog/uk/prototype.database.txt @@ -67,7 +67,7 @@ return array( Однак, у більшості випадків, підключення до БД використовується не безпосередньо, а через [ActiveRecord](/doc/guide/uk/database.ar). -Зупинимося докладніше на властивості `tablePrefix`, яке ми використовували під час налаштування. +Зупинимося докладніше на властивості `tablePrefix`, яку ми використовували під час налаштування. Дана властивість задає префікс `tbl_` для таблиць БД. Тобто, якщо імʼя таблиці у SQL-виразі укладено у подвійні фігурні дужки (наприклад, `{{post}}`), то компонент `db`, перед тим, як відправити запит СУБД, diff --git a/docs/blog/uk/prototype.scaffold.txt b/docs/blog/uk/prototype.scaffold.txt index 9ba934a46d..43758eb527 100644 --- a/docs/blog/uk/prototype.scaffold.txt +++ b/docs/blog/uk/prototype.scaffold.txt @@ -146,7 +146,7 @@ http://www.example.com/blog/index.php?r=comment [додатка](/doc/guide/uk/basics.application) для обробки запиту; 2. Додаток створює екземпляр класу `PostController` та виконує його; 3. Екземпляр класу `PostController` виконує дію `index` (метод `actionIndex()`). - Відзначимо, що `index` є дією за замовчуванням та використовується у разі + Відзначимо, що `index` є дією за замовчуванням та використовується у разі, якщо користувач не вказав дію у URL; 4. Метод `actionIndex()` робить запит до бази даних для отримання списку останніх записів; 5. Метод `actionIndex()` генерує представлення `index` з даними записів. diff --git a/docs/blog/uk/start.testdrive.txt b/docs/blog/uk/start.testdrive.txt index 588f5a8d17..ebc053169f 100644 --- a/docs/blog/uk/start.testdrive.txt +++ b/docs/blog/uk/start.testdrive.txt @@ -27,7 +27,7 @@ Скористаємося утилітою командного рядка `yiic`, що йде в комплекті з фреймворком, для створення основи додатка в директорії `/wwwroot/blog`. -`yiic` може бути використана для генерації коду, що дозволяє зменшити обсяг повторюваного коду, що набирається вручну. +`yiic` може бути використана для генерації коду, що дозволяє зменшити обсяг повторюваного коду, який набирається вручну. Відкриємо вікно командного рядка і виконаємо наступну команду: @@ -75,7 +75,7 @@ Yii::createWebApplication($config)->run(); Також, у нас є [базова директорія додатку](/doc/guide/uk/basics.application#application-base-directory) `/wwwroot/blog/protected`. Більша частина нашого коду і даних буде зберігатися в цій директорії і повинна бути захищена від прямого доступу користувачів із веб. -Для [веб-сервера Apache](http://httpd.apache.org/) ми розміщаємо в цю директорію файл `.htaccess` із наступним змістом: +Для [веб-сервера Apache](http://httpd.apache.org/) ми кладемо в цю директорію файл `.htaccess` із наступним змістом: ~~~ deny from all diff --git a/docs/css/main.css b/docs/css/main.css index 00177540ae..2406e8e91d 100644 --- a/docs/css/main.css +++ b/docs/css/main.css @@ -61,7 +61,7 @@ pre { float: right; width: 200px; - padding: 15px 10px;border: + padding: 15px 10px; } .page #content diff --git a/docs/guide/basics.application.txt b/docs/guide/basics.application.txt index 0a74a55b55..cc0fb4034a 100644 --- a/docs/guide/basics.application.txt +++ b/docs/guide/basics.application.txt @@ -212,7 +212,7 @@ life cycle: 0. Pre-initialize the application with [CApplication::preinit()]; - 1. Set up the class autoloader and error handling; + 1. Set up the error handling; 2. Register core application components; diff --git a/docs/guide/caching.data.txt b/docs/guide/caching.data.txt index 32816e7920..919fb6201d 100644 --- a/docs/guide/caching.data.txt +++ b/docs/guide/caching.data.txt @@ -18,7 +18,7 @@ The cached data will remain in the cache forever unless it is removed because of some caching policy (e.g. caching space is full and the oldest data are removed). To change this behavior, we can also supply an expiration parameter when calling [set()|CCache::set] so that the data will -be removed from the cache after a certain period of time: +be removed from the cache after, at most, that period of time: ~~~ [php] @@ -28,8 +28,8 @@ Yii::app()->cache->set($id, $value, 30); Later when we need to access this variable (in either the same or a different Web request), we call [get()|CCache::get] with the ID to retrieve -it from cache. If the value returned is false, it means the value is not -available in cache and we should regenerate it. +it from cache. If the returned value is false, it means the value is not +available in cache and we have to regenerate it. ~~~ [php] @@ -51,7 +51,7 @@ applications. Some cache storages, such as MemCache, APC, support retrieving multiple cached values in a batch mode, which may reduce the overhead involved in retrieving cached data. A method named -[mget()|CCache::mget] is provided to exploit this feature. In case the underlying +[mget()|CCache::mget] is provided to achieve this feature. In case the underlying cache storage does not support this feature, [mget()|CCache::mget] will still simulate it. @@ -126,7 +126,7 @@ in future, as the result can be directly served from the cache. > Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) > also support query caching on the DB server side. Compared with the server-side > query caching, the same feature we support here offers more flexibility and -> potentially may be more efficient. +> may be potentially more efficient. ### Enabling Query Caching @@ -252,4 +252,4 @@ of each entry to be 1MB. Therefore, if the size of a query result exceeds this l the caching will fail. -
$Id$
\ No newline at end of file +
$Id$
diff --git a/docs/guide/caching.overview.txt b/docs/guide/caching.overview.txt index 3548640f3a..74fd687e41 100644 --- a/docs/guide/caching.overview.txt +++ b/docs/guide/caching.overview.txt @@ -58,7 +58,7 @@ can explicitly specify a database for it to use by setting its - [CWinCache]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) ([see also](http://php.net/manual/en/book.wincache.php)) extension. - - [CFileCache]: uses files to store cached data. This is particular suitable to + - [CFileCache]: uses files to store cached data. This is particularly suitable to cache large chunk of data (such as pages). - [CDummyCache]: presents dummy cache that does no caching at all. The purpose diff --git a/docs/guide/changes.txt b/docs/guide/changes.txt index 9566db8cfb..dca037292f 100644 --- a/docs/guide/changes.txt +++ b/docs/guide/changes.txt @@ -3,6 +3,11 @@ New Features This page summarizes the main new features introduced in each Yii release. +Version 1.1.15 +-------------- + + * [Added AR post-JOIN operations](/doc/guide/database.arr#post-join-operations) + Version 1.1.14 -------------- diff --git a/docs/guide/database.ar.txt b/docs/guide/database.ar.txt index 2fb1a795d7..db6fcec216 100644 --- a/docs/guide/database.ar.txt +++ b/docs/guide/database.ar.txt @@ -77,6 +77,7 @@ Support for AR is limited by DBMS. Currently, only the following DBMS are supported: - [MySQL 4.1 or later](http://www.mysql.com) + - [MariaDB](https://mariadb.com) - [PostgreSQL 7.3 or later](http://www.postgres.com) - [SQLite 2 and 3](http://www.sqlite.org) - [Microsoft SQL Server 2000 or later](http://www.microsoft.com/sqlserver/) diff --git a/docs/guide/database.arr.txt b/docs/guide/database.arr.txt index bb5093e8eb..06caa272bd 100644 --- a/docs/guide/database.arr.txt +++ b/docs/guide/database.arr.txt @@ -299,6 +299,13 @@ For more details, see the section "Relational Query Performance". - `join`: the extra `JOIN` clause. It defaults to empty. This option has been available since version 1.1.3. + - `joinOptions`: the property for setting post-`JOIN` operations such as +`USE INDEX`. String typed value can be used with `JOIN`s for `HAS_MANY` and `MANY_MANY` +relations, while array typed value designed to be used only with `MANY_MANY` relations. +First array element will be used for junction table `JOIN` and second array element +will be used for target table `JOIN`. This option has been available since version +1.1.15. + - `group`: the `GROUP BY` clause. It defaults to empty. Column names referenced in this option should be disambiguated. @@ -400,10 +407,10 @@ $posts=Post::model()->with('comments')->findAll(array( > > ~~~ > [php] -> $comments=Comment::model()->with( +> $comments=Comment::model()->with(array( > 'author', > 'post', -> 'post.author'=>array('alias'=>'p_author'))->findAll(array( +> 'post.author'=>array('alias'=>'p_author')))->findAll(array( > 'order'=>'author.name, p_author.name, post.title' > )); > ~~~ @@ -798,3 +805,135 @@ class User extends CActiveRecord $teacher=User::model()->findByPk(1); $students=$teacher->students; ~~~ + +Post-JOIN operations +-------------------- + +Since 1.1.15 additional post-JOIN operations could be set. +`CBaseActiveRelation::$joinOptions` has been added. +Consider we have the following models and relations: + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts' => array(self::HAS_MANY, 'Post', 'user_id'), + ); + } +} + +class Post extends CActiveRecord +{ + public function relations() + { + return array( + 'user' => array(self::BELONGS_TO, 'User', 'user_id'), + 'tags' => array(self::MANY_MANY, 'Tag', '{{post_tag}}(post_id, tag_id)'), + ); + } +} + +class Tag extends CActiveRecord +{ + public function relations() + { + return array( + 'posts' => array(self::MANY_MANY, 'Post', '{{post_tag}}(tag_id, post_id)'), + ); + } +} +~~~ + +Query code examples with `USE INDEX` clauses: + +~~~ +[php] +$users=User::model()->findAll(array( + 'select'=>'t.id,t.name', + 'with'=>array( + 'posts'=>array( + 'alias'=>'p', + 'select'=>'p.id,p.title', + 'joinOptions'=>'USE INDEX(post__user)', + ), + ), +)); + +$posts=Post::model()->findAll(array( + 'select'=>'t.id,t.title', + 'with'=>array( + 'tags'=>array( + 'alias'=>'a', + 'select'=>'a.id,a.name', + 'joinOptions'=>'USE INDEX(post_tag__tag) USE INDEX(post_tag__post)', + ), + ), +)); + +$posts=Post::model()->findAll(array( + 'select'=>'t.id,t.title', + 'with'=>array( + 'tags'=>array( + 'alias'=>'a', + 'select'=>'a.id,a.name', + 'joinOptions'=>array( + 'USE INDEX(post_tag__tag) USE INDEX(post_tag__post)', + 'USE INDEX(tag__name)', + ), + ), + ), +)); +~~~ + +Code above should generate following MySQL queries respectively: + +~~~ +[sql] +SELECT + `t`.`id` AS `t0_c0`, `t`.`name` AS `t0_c1`, + `p`.`id` AS `t1_c0`, `p`.`title` AS `t1_c2` +FROM `tbl_user` `t` +LEFT OUTER JOIN `tbl_post` `p` + USE INDEX(post__user) ON (`p`.`user_id`=`t`.`id`); + +SELECT + `t`.`id` AS `t0_c0`, `t`.`title` AS `t0_c2`, + `a`.`id` AS `t1_c0`, `a`.`name` AS `t1_c1` +FROM `tbl_post` `t` +LEFT OUTER JOIN `tbl_post_tag` `tags_a` + USE INDEX(post_tag__tag) USE INDEX(post_tag__post) ON (`t`.`id`=`tags_a`.`post_id`) +LEFT OUTER JOIN `tbl_tag` `a` ON (`a`.`id`=`tags_a`.`tag_id`); + +SELECT + `t`.`id` AS `t0_c0`, `t`.`title` AS `t0_c2`, + `a`.`id` AS `t1_c0`, `a`.`name` AS `t1_c1` +FROM `tbl_post` `t` +LEFT OUTER JOIN `tbl_post_tag` `tags_a` + USE INDEX(post_tag__tag) USE INDEX(post_tag__post) ON (`t`.`id`=`tags_a`.`post_id`) +LEFT OUTER JOIN `tbl_tag` `a` + USE INDEX(tag__name) ON (`a`.`id`=`tags_a`.`tag_id`); +~~~ + +The `$joinOptions` query option could also be set in relation declarations as follows: + +~~~ +[php] +class Post extends CActiveRecord +{ + public function relations() + { + return array( + 'user' => array(self::BELONGS_TO, 'User', 'user_id'), + 'tags' => array(self::MANY_MANY, 'Tag', '{{post_tag}}(post_id, tag_id)', + 'joinOptions' => array( + 'USE INDEX(post_tag__tag) USE INDEX(post_tag__post)', + 'USE INDEX(tag__name)', + ), + ), + ); + } +} +~~~ diff --git a/docs/guide/database.dao.txt b/docs/guide/database.dao.txt index 1826bce783..7a7c938e6d 100644 --- a/docs/guide/database.dao.txt +++ b/docs/guide/database.dao.txt @@ -48,7 +48,7 @@ documentation](http://www.php.net/manual/en/pdo.construct.php) for complete information. Below is a list of commonly used DSN formats: - SQLite: `sqlite:/path/to/dbfile` - - MySQL: `mysql:host=localhost;dbname=testdb` + - MySQL/MariaDB: `mysql:host=localhost;dbname=testdb` - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb` - SQL Server: `mssql:host=localhost;dbname=testdb` - Oracle: `oci:dbname=//localhost:1521/testdb` diff --git a/docs/guide/database.migration.txt b/docs/guide/database.migration.txt index 6456b45acd..f7ea946d99 100644 --- a/docs/guide/database.migration.txt +++ b/docs/guide/database.migration.txt @@ -166,8 +166,9 @@ When Yii performs the migration, it will start a DB transaction and then call `s > Note: Not all DBMS support transactions. And some DB queries cannot be put > into a transaction. In this case, you will have to implement `up()` and -> `down()`, instead. And for MySQL, some SQL statements may cause -> [implicit commit](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html). +> `down()`, instead. And for MySQL and MariaDB, some SQL statements may cause implicit commit +> (see documentation of [MySQL](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) and +> [MariaDB](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/) for details). Applying Migrations diff --git a/docs/guide/de/quickstart.apache-nginx-config.txt b/docs/guide/de/quickstart.apache-nginx-config.txt index cb9ea9fe64..f3e98ffc4b 100644 --- a/docs/guide/de/quickstart.apache-nginx-config.txt +++ b/docs/guide/de/quickstart.apache-nginx-config.txt @@ -7,7 +7,7 @@ Apache Yii kann bereits mit einem standardmäßig konfigurierten Apache Webserver betrieben werden. Die `.htaccess`-Dateien in Yii-Framework- und Anwendungsverzeichnissen sperren den Zugriff auf vertrauliche Dateien. Um das -Starscript (normalerweise `index.php`) in URLs zu unterdrücken, kann man +Startscript (normalerweise `index.php`) in URLs zu unterdrücken, kann man `mod_rewrite`-Anweisungen in die `.htaccess`-Datei im Webverzeichnis oder in die Virtual-Host-Konfiguration einfügen: diff --git a/docs/guide/extension.integration.txt b/docs/guide/extension.integration.txt index f92090622e..2d8258f7c9 100644 --- a/docs/guide/extension.integration.txt +++ b/docs/guide/extension.integration.txt @@ -66,6 +66,44 @@ Yii::setPathOfAlias('Imagine',Yii::getPathOfAlias('application.vendors.Imagine') In the code above the name of the alias we've defined should match the first namespace part used in the library. +Using 3rd-Party Autoloaders +--------------------------- + +Some 3rd-Party libraries (for example PHPUnit) use their own class autoloaders, which perform +class file inclusion by rules, which are different from the ones used in Yii autoloader. +Since Yii uses PHP include path as a 'last source' of class files, registering such 3rd-party +autoloaders may produce a PHP Warning: +~~~ +include(PHPUnit_Framework_TestCase.php) [function.include]: failed to open stream: No such file or directory +~~~ +In order to avoid such problem make sure any 3rd-party class autoloaders are registered +before the Yii autoloader: +~~~ +[php] +require_once('PHPUnit/Autoload.php'); // register 3rd-party autoloader +require_once('/path/to/framework/yii.php'); // register Yii autoloader +... +~~~ +If 3rd-party class autoloader is coming as separated function or method, you may use +`Yii::registerAutoloader()` method to register it. In this case Yii will prepend it +before own autoloader automatically. +~~~ +[php] +require_once('/path/to/framework/yii.php'); // register Yii autoloader +... +Yii::registerAutoloader(array('SomeLibrary','autoload')); // register 3rd-party autoloader +... +~~~ +You can also avoid problems with 3rd-party autoloader disabling usage of PHP include path +by setting `YiiBase::$enableIncludePath` to `false` before starting application: +~~~ +[php] +require_once('/path/to/framework/yii.php'); +$configFile='/path/to/config/main.php'; +Yii::$enableIncludePath = false; // disable PHP include path usage +Yii::createWebApplication($configFile)->run(); +~~~ + Using Yii in 3rd-Party Systems ------------------------------ diff --git a/docs/guide/fr/basics.application.txt b/docs/guide/fr/basics.application.txt index c5d3f37a52..1e4621ca7d 100644 --- a/docs/guide/fr/basics.application.txt +++ b/docs/guide/fr/basics.application.txt @@ -2,13 +2,13 @@ Application =========== L'application représente le contexte d'exécution de gestion des requêtes. -Son principal objet est de résoudre les requêtes utilisateurs et de les +Son rôle principal est de résoudre les requêtes utilisateurs et de les rediriger vers le contrôleur adéquat. Elle sert aussi à centraliser toutes les informations de configuration qui lui sont relatives. -C'est pour cela que l'application peut aussi être appelé `front-controller` +C'est pour cela que l'application peut aussi être appelée `front-controller` L'application est un singleton créé par le [script de démarrage](/doc/guide/basics.entry). -Le singleton peut être accédé de n'importe ou en utilisant [Yii::app()|YiiBase::app]. +Le singleton peut être accédé de n'importe où en utilisant [Yii::app()|YiiBase::app]. Configuration de l'Application ------------------------------ @@ -20,7 +20,7 @@ propriétés de l'instance à sa création. Lorsque le paramétrage par fichier de configuration est inadapté ou insuffisant, il est possible d'étendre directement [CWebApplication]. -La configuration est un tableau de paires clef-valeur ou chaque clef représente +La configuration est un tableau de paires clef-valeur où chaque clef représente le nom d'une propriété de l'instance de l'application et chaque valeur, la valeur à affecter lors de l'initialisation. Par exemple, la configuration suivante permet de paramétrer les @@ -63,7 +63,7 @@ Dossier de base de l'Application -------------------------------- Le dossier de base de l'application est le dossier racine qui contient tous les -élément sensibles de l'application (scripts PHP, données, ...). En standard, +éléments sensibles de l'application (scripts PHP, données, ...). En standard, c'est le sous dossier `protected` qui se trouve à l'intérieur du dossier qui contient le script de démarrage. Ce dossier peut être changé en modifiant la propriété [basePath|CWebApplication::basePath] dans le fichier de [configuration de l'application](/doc/guide/basics.application#application-configuration). @@ -81,14 +81,12 @@ Composant d'Application Une application peut être aisément modifiée et enrichie de nouvelles fonctionnalités grâce à son architecture très souple basée sur des composants. -En pratique, une application fédère un batterie de composants ou chacun prend en charge -une fonctionnalité spécifique. +En pratique, une application fédère une batterie de composants où chacun prend en charge une fonctionnalité spécifique. Par exemple, l'application résoud une requête utilisateur grâce aux composants [CUrlManager] et [CHttpRequest]. -Il est possible de paramétrer les propriétés de tous les [composants|CApplication::components] d'une -application, Par example, il est possible de configurer le composant -[CMemCache] pour qu'il utilise plusieurs serveurs de cache, +Il est possible de paramétrer les propriétés de chacun des [composants|CApplication::components] d'une +application, Par exemple, il est possible de configurer le composant [CMemCache] pour qu'il utilise plusieurs serveurs de cache. ~~~ [php] @@ -108,31 +106,30 @@ array( ~~~ Dans la configuration ci-dessus, l'élément `cache` a été ajouté à la liste -des `components`. Le sous-élément `cache` défini la classe de cache à utiliser, -ici `CMemCache` et la liste des serveurs `servers` avec leur paramètres à utiliser. +des `components`. Le sous-élément `cache` définit la classe de cache à utiliser, +ici `CMemCache` et la liste des serveurs `servers` à utiliser avec leurs paramètres. Pour accéder à un composant de l'application, il suffit d'effectuer l'appel -`Yii::app()->ComponentID`, ou `ComponentID` correspond à l'ID du composant. -Pour l'exemple précédent, l'accès au composant `cache` se ferait tout simplement +`Yii::app()->ComponentID`, où `ComponentID` correspond à l'ID du composant. +Pour l'exemple précédent, l'accès au composant `cache` se fait tout simplement par l'appel `Yii::app()->cache`. Un composant peut être désactivé en forçant sa propriété `enabled` à false. -Lors de l'accès au composant, si il a été désactivé, la valeur null est retournée. +Lors de l'accès à un composant qui a été désactivé, la valeur null est retournée. > Tip|Astuce : En standard, les composants de l'application sont créés à la demande. -Cela signifie qu'un composant de l'application n'est pas instancié si -il n'est pas accédé. Cela permet de ne pas dégrader les performances de -l'application même si beaucoup de composants sont déclarés. Pour certains composants -tel que le [CLogRouter], il peut être nécessaire de les instancier, -même si il ne sont pas accédés pendant le cycle de vie de l'application. Pour ce -faire, il suffit de les déclarer dans la section [preload|CApplication::preload] du +Cela signifie qu'un composant de l'application n'est pas instancié avant son utilisation, i.e. avant son accés. +Cela permet de ne pas dégrader les performances de l'application même dans les cas où beaucoup +de composants sont déclarés. Pour certains composants tels que le [CLogRouter], il peut toutefois être nécessaire +de les instancier dès le démarrage de l'application, même s'ils ne sont pas référencés explicitement pendant le cycle de vie de l'application. +Pour ce faire, il suffit de les déclarer dans la section [preload|CApplication::preload] du paramétrage de l'application. Composants du noyau de l'application ------------------------------------ Par défaut, Yii prédéfini les propriétés de toute une série de composants -communs aux applications web. C'est, par exemple, le cas du composant +communs aux applications Web. C'est, par exemple, le cas du composant [request|CWebApplication::request] chargé de gérer les requêtes utilisateur. En paramétrant ces composants, il est possible d'adapter quasiment tous les aspects du comportement de Yii. @@ -154,9 +151,9 @@ lors de l'accès au composant. gestions des scripts client (javascripts et CSS). - [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] - -fourni la traduction des messages utilisés par le noyau de Yii. +fournit la traduction des messages utilisés par le noyau de Yii. - - [db|CApplication::db]: [CDbConnection] - fourni la connexion à la + - [db|CApplication::db]: [CDbConnection] - fournit la connexion à la base de données. Attention, il faut définir correctement la propriété [connectionString|CDbConnection::connectionString] avant d'utiliser le composant. @@ -168,25 +165,21 @@ des erreurs PHP et des exceptions. l'affichage. - [messages|CApplication::messages]: [CPhpMessageSource] - -fourni la traduction des messages utilisés par Yii. +fournit la traduction des messages utilisés par Yii. - - [request|CWebApplication::request]: [CHttpRequest] - fourni -les fonctionnalités relatives aux requêtes utilisateurs. + - [request|CWebApplication::request]: [CHttpRequest] - fournit les fonctionnalités relatives aux requêtes utilisateurs. - [securityManager|CApplication::securityManager]: [CSecurityManager] - -fourni les services de sécurity tels que le hashage ou le cryptage de données. +fournit des services de sécurité tels que le hashage ou le cryptage de données. - - [session|CWebApplication::session]: [CHttpSession] - fourni -les fonctionnalités relatives aux sessions. + - [session|CWebApplication::session]: [CHttpSession] - fournit les fonctionnalités relatives aux sessions. - [statePersister|CApplication::statePersister]: [CStatePersister] - -fourni une méthode globale de gestion de la persistance des données. +fournit une méthode globale de gestion de la persistance des données. - - [urlManager|CWebApplication::urlManager]: [CUrlManager] - fourni -les fonctionnalités de création et d'interprétation des URLs. + - [urlManager|CWebApplication::urlManager]: [CUrlManager] - fournit les fonctionnalités de création et d'interprétation des URLs. - - [user|CWebApplication::user]: [CWebUser] - information relative -à l'identité de l'utilisateur courant. + - [user|CWebApplication::user]: [CWebUser] - informations relatives à l'identité de l'utilisateur courant. - [themeManager|CWebApplication::themeManager]: [CThemeManager] - gestion des thèmes. @@ -209,13 +202,13 @@ par les étapes suivantes : - Enregistrement des "comportements" de l'application; - Chargement des composants statiques; - 5. Levée de l'évènement [onBeginRequest|CApplication::onBeginRequest]; + 5. Levée de l’événement [onBeginRequest|CApplication::onBeginRequest]; 6. Traitement de la requête utilisateur: - Résolution de la requête; - Création du contrôleur; - Exécution du contrôleur; - 7. Levée de l'évènement [onEndRequest|CApplication::onEndRequest]; + 7. Levée de l’événement [onEndRequest|CApplication::onEndRequest]; -
$Id: basics.application.txt 1906 $
+
$Id: basics.application.txt 1911 2013-11-20 $
diff --git a/docs/guide/fr/basics.best-practices.txt b/docs/guide/fr/basics.best-practices.txt index d7453d8c9b..bcb70758f1 100644 --- a/docs/guide/fr/basics.best-practices.txt +++ b/docs/guide/fr/basics.best-practices.txt @@ -1,16 +1,16 @@ -Bonnes Pratiques MVC +Bonnes Pratiques MVC ==================== -Bien que l'architecture Modèle-Vue-Contrôleur (MVC) soit connu par la quasi totalité des dévelopeur web, de nombreuses personnes s'interrogent encore sur la meilleure facon de l'appliquer lors du développement d'une application. Les idées centrales derrière MVC sont **la réutilisation et le séparation logique du code**. Nous allons voir dans cette section les règles générales à suivre afin de respecter au mieux l'architecture MVC lors de développement d'une application Yii. +Bien que l'architecture Modèle-Vue-Contrôleur (MVC) soit connue de la quasi totalité des développeurs web, de nombreuses personnes s'interrogent encore sur la meilleure façon de l'appliquer lors du développement d'une application. Les idées centrales derrière MVC sont **la réutilisation et le séparation logique du code**. Nous allons voir dans cette section les règles générales à suivre afin de respecter au mieux l'architecture MVC lors du développement d'une application Yii. -Afin de mieux expliquer ces règles, on partira sur le principe qu'une application web est constituée de différentes sous applications tels que: +Afin de mieux expliquer ces règles, on partira sur le principe qu'une application web est constituée de différentes sous-applications telles que: * front end: le site web auquel les utilisateurs ont accès; * back end: le site permettant d'administrer l'application. Habituellement doté d'un accès restreint; -* console: une application constituée de commandes à lancer depuis un terminal ou au travers d´une tache planifiée; -* Web API: fourni des interfaces pour les applications tièrces qui souhaitent s'intégrer avec l'application. +* console: une application constituée de commandes à lancer depuis un terminal ou au travers d'une tâche planifiée; +* Web API: fourni des interfaces pour les applications tierces qui souhaitent s'intégrer avec l'application. -Les sous-applications pourront être implémentées en tant que [modules](/doc/guide/basics.module), ou comme une application Yii partageant une partie du code avec d'autre sous-applications. +Les sous-applications pourront être implémentées en tant que [modules](/doc/guide/basics.module), ou comme une application Yii partageant une partie du code avec d'autres sous-applications. Modèle @@ -25,10 +25,10 @@ et le front/back end d'une application. Ainsi les modèles: * doivent contenir la logique applicative (par exemple les règles de validation) afin d'assurer que les données affichées sont bien conformes avec les spécifications; -* peuvent contenir du code pour manipuler des données. Par exemple, un modèle `SearchForm`, au delà d'afficher les données issue d'une recherche, peut contenir une méthode `search` qui implémente la recherche effective. +* peuvent contenir du code pour manipuler des données. Par exemple, un modèle `SearchForm`, au delà d'afficher les données issues d'une recherche, peut contenir une méthode `search` qui implémente la recherche effective. Parfois, suivre cette dernière règle abouti à un modèle très lourd, contenant trop de code pour une unique classe. Cela peut aussi rendre -le modèle difficile à maintenir si le code est utilisé dans différents buts. Par example, un modèle `News` peut contenir une méthode +le modèle difficile à maintenir si le code est utilisé dans différents buts. Par exemple, un modèle `News` peut contenir une méthode appelée `getLatestNews` qui sera utilisée seulement par le front end; il pourrait aussi contenir une méthode appelée `getDeletedNews` qui ne serait utilisée que par le back end. Cela ne pose pas de problème pour une petite ou moyenne application. Pour les applications plus importantes, la technique suivante pourra être utilisée pour rendre les modèles plus maintenables: @@ -39,7 +39,7 @@ qui ne serait utilisée que par le back end. Cela ne pose pas de problème pour Ainsi, si l'on veut utiliser cette technique, on ajoute un modèle `News` dans l'application front end contenant seulement la méthode `getLatestNews`, et on ajoute un autre modèle `News` dans l'application back end, qui lui ne contiendrait que la méthode `getDeletedNews`. -En général, les modèles ne doivent pas contenir la logique gérant l'interaction avec les utilisateurs. De manière plus spécifiques, les modèles: +En général, les modèles ne doivent pas contenir la logique gérant l'interaction avec les utilisateurs. De manière plus spécifique, les modèles: * ne doivent pas utiliser `$_GET`, `$_POST`, ou d'autres variables similaires liées étroitement à la requête. Il ne faut pas oublier qu'un modèle peut être utilisé par une sous-application complètement différente (par exemple des tests unitaires, une API) qui n'utilise pas ces variables pour représenter les requêtes utilisateurs. Ces variables provenant de la requête utilisateur doivent être gérées par le contrôleur. @@ -51,43 +51,43 @@ Vue Les [vues](/doc/guide/basics.view) sont en charge de la représentation des modèles dans le format souhaité par l'utilisateur. En général, les vues: -* doivent principalement contenir du code d'affichage, tel que du HTML, et du code PHP simple permettant de parcourir, formatter et afficher des données; +* doivent principalement contenir du code d'affichage, tel que du HTML, et du code PHP simple permettant de parcourir, formater et afficher des données; * doivent éviter d'exécuter des requêtes sur la BD. Ce type de code se trouve normalement dans les modèles. * doivent éviter l'accès direct à `$_GET`, `$_POST`, ou d'autres variables similaires représentant la requête de l'utilisateur. C'est le rôle du -contrôleur. La vue doit être centrée sur de l'affichage et sur la structure des données fournies par le contrôleur et/ou le modèle, et non pas -accéder directement aux données de la requête ou de la base de donnée. +contrôleur. La vue doit être centrée sur l'affichage et sur la structure des données fournies par le contrôleur et/ou le modèle, et non pas +accéder directement aux données de la requête ou de la base de données. * peuvent accéder à des propriétés et méthodes des contrôleurs et modèles directement. Cependant, ces accès ne doivent être fait que dans un but d'affichage. -Les vues peuvent être réutilisées de différent moyens: +Les vues peuvent être réutilisées de différents moyens: -* Layout (Structure): les zone d'affichage communes (par exemple en-tete et pied de page) peuvent être décrites dans les vues layout. +* Layout (Structure): les zones d'affichages communes (par exemple en-tête et pied de page) peuvent être décrites dans les vues layout. * Vues partielles: les vues partielles (vues n'utilisant pas les layouts) sont utiles pour réutiliser des fragments de code d'affichage. Par exemple, on peut utiliser la vue partielle `_form.php` pour afficher les champs d'entrées d'un formulaire qui sera utilisé aussi bien dans les pages de création du modèle que dans celles de mise à jour. -* Widgets: dans les cas ou beaucoup de logique est nécessaire pour afficher une vue partielle, cette vue peut être transformée en widget dont sa classe sera la plus à meme de recevoir cette logique. Pour les widgets qui génèrent beaucoup de code HTML, il est préférable d'utiliser des fichiers spécifiques à ce widget pour cette vue et contenant ce code HTML. +* Widgets: dans les cas où beaucoup de logique est nécessaire pour afficher une vue partielle, cette vue peut être transformée en widget dont sa classe sera la plus à même de recevoir cette logique. Pour les widgets qui génèrent beaucoup de code HTML, il est préférable d'utiliser des fichiers spécifiques à ce widget pour cette vue et contenant ce code HTML. -* Classes Helper: il est souvent utile dans les vues de recourir à du code très court spécifique à de petites taches telles que le formatage de données ou la génération de tags HTML. Plutot que de placer ce code directement dans les fichiers de vues, une meilleur approche est de le mettre dans une classe helper. Ensuite, il vous suffit d'utiliser ces classes dans vos fichiers de vues. Yii fournit un exemple pour cette approche. Yii a une classe helper [CHtml] tres puissante pouvant produire du code HTML souvant utilisé. Les classes Helper peuvent être déposées dans le [répertoire de chargement automatique](/doc/guide/basics.namespace) afin qu'elles soient disponibles sans les inclure explicitement. +* Classes Helper: il est souvent utile dans les vues de recourir à du code très court spécifique à de petites tâches telles que le formatage de données ou la génération de tags HTML. Plutôt que de placer ce code directement dans les fichiers de vues, une meilleur approche est de le mettre dans une classe helper. Ensuite, il vous suffit d'utiliser ces classes dans vos fichiers de vues. Yii fournit un exemple pour cette approche. Yii a une classe helper [CHtml] très puissante pouvant produire du code HTML souvent utilisé. Les classes Helper peuvent être déposées dans le [répertoire de chargement automatique](/doc/guide/basics.namespace) afin qu'elles soient disponibles sans les inclure explicitement. Contrôleur ---------- -Un [contrôleur](/doc/guide/basics.controller) est la pièce maitresse reliant les modèles, vues et autres composants afin de former une application complète. Les contrôleurs sont en charge de la gestion directe des requêtes utilisateurs. Ainsi, les contrôleurs: +Un [contrôleur](/doc/guide/basics.controller) est la pièce maîtresse reliant les modèles, vues et autres composants afin de former une application complète. Les contrôleurs sont en charge de la gestion directe des requêtes utilisateurs. Ainsi, les contrôleurs: * peuvent accéder a `$_GET`, `$_POST` et autres variables PHP représentant les requêtes utilisateurs; -* peuvent instancier des modèles et gérer leur cycle de vie. Par exemple, dans une action typique de mise à jour d'un modèle, un contrôleur pourra tout d'abord créer une instance du modèle; puis remplir le modèle avec les entrées utilisateurs à partir de `$_POST`; et après avoir enregistré le modèle, le contrôleur pourra rediriger le navigateur de l'utilisateur vers la page affichant les détails du modèle. A noter que dans cette implémentation l'enregistrement du modèle doit se trouver dans le modèle plutot que dans le contrôleur. +* peuvent instancier des modèles et gérer leur cycle de vie. Par exemple, dans une action typique de mise à jour d'un modèle, un contrôleur pourra tout d'abord créer une instance du modèle; puis remplir le modèle avec les entrées utilisateurs à partir de `$_POST`; et après avoir enregistré le modèle, le contrôleur pourra rediriger le navigateur de l'utilisateur vers la page affichant les détails du modèle. A noter que dans cette implémentation l'enregistrement du modèle doit se trouver dans le modèle plutôt que dans le contrôleur. * doivent éviter de contenir des requêtes SQL, dont leur place réside dans les modèles. * doivent éviter de contenir du code HTML ou tout autre code d'affichage. Ce code est normalement situé dans les vues. -Dans une application MVC bien conçue, les contrôleurs sont souvent très léger, contenant seulement quelques douzaines de lignes de code; alors que les modèles sont plus importants en taille, contenant la plupart du code responsable pour afficher et manipuler les données. En effet cela est du au fait que les structures de données et la logique applicative représentés par les modèles sont en général très spécifiques à une application particulière, et ont besoin d'être grandement adaptés pour répondre aux besoins de l'application; alors que la logique du contrôleur suit souvent des patterns similaires entre applications et peut ainsi être simplifiée par le framework sous-jacent ou les classes de base. +Dans une application MVC bien conçue, les contrôleurs sont souvent très légers, contenant seulement quelques douzaines de lignes de code; alors que les modèles sont plus importants en taille, contenant la plupart du code responsable de l'affichage et de la manipulation des données. En effet cela est dû au fait que les structures de données et la logique applicative représentées par les modèles sont en général très spécifiques à une application particulière, et ont besoin d'être grandement adaptées pour répondre aux besoins de l'application; alors que la logique du contrôleur suit souvent des modèles de conception similaires entre applications et peut ainsi être simplifiée par le framework sous-jacent ou les classes de base. -
$Id: basics.best-practices.txt 2795 $
+
$Id: basics.best-practices.txt 2797 2013-11-20 $
diff --git a/docs/guide/fr/basics.component.txt b/docs/guide/fr/basics.component.txt index cd29b9baaa..bbc757b23d 100644 --- a/docs/guide/fr/basics.component.txt +++ b/docs/guide/fr/basics.component.txt @@ -1,22 +1,23 @@ Composant ========= -Les applications Yii sont construites sur des composants. +Les applications Yii sont construites à partir de composants, i.e. d'objets +écrits dans le but de répondre à des spécifications et un cahier des charges précis. Un composant est une instance de [CComponent] ou d'une de ses classes dérivées. -L'utilisation d'un composant implique l'accès à ses propriétés et la gestion de -ses événements. La classe de base [CComponent] spécifie comment définir les +L'utilisation d'un composant consiste principalement à accéder à ses propriétés et +utiliser ses événements. La classe de base [CComponent] spécifie comment définir les propriétés et les événements. -Propriété d'un composant ------------------------- +Définir et utiliser la propriété d'un composant +----------------------------------------------- -Une propriété d'un composant fonctionne comme les propriétés publique d'un +Une propriété d'un composant est similaire à une variable publique d'un objet. Il est possible d'y lire ou d'y assigner une valeur. Par exemple, ~~~ [php] $width=$component->textWidth; // récupère la propriété textWidth -$component->enableCaching=true; // défini la propriété enableCaching +$component->enableCaching=true; // définit la propriété enableCaching ~~~ Pour définir une propriété, il est possible soit de déclarer une @@ -27,21 +28,21 @@ les getter et setter correspondant comme ceci: [php] public function getTextWidth() { - return $this->_textWidth; + return $this->_textWidth; } public function setTextWidth($value) { - $this->_textWidth=$value; + $this->_textWidth=$value; } ~~~ -Le fragment de code ci dessus défini un accès en lecture/écriture +Le fragment de code ci-dessus définit un accès en lecture/écriture à la propriété nommée `textWidth` (le nom n'est pas sensible à la casse). -Lors de la lecture de la propriété, `getTextWidth()` est appelé et la +Lors de la lecture de la propriété, `getTextWidth()` est appelée et la valeur retournée devient la valeur de la propriété; le fonctionnement est similaire lorsque l'on y accède en écriture, la méthode `setTextWidth()` est -appelé. Si le setter n'est pas défini, la propriété est accessible en +appelée. Si le setter n'est pas défini, la propriété est accessible en lecture seule et un accès en écriture lève une exception. L'utilisation de getter et setter à l'avantage de permettre d'insérer de la logique de traitement lors de l'accès à la propriété (e.g. effectuer une validation, lever un événement). @@ -55,16 +56,18 @@ pas le cas pour une variable de classe. Evénement et composant ---------------------- -Les évènements d'un composant sont des propriétés spéciales qui prennent pour -valeur des méthodes (appelées `gestionnaires d'évènements`). Le fait d'attacher -une méthode à un évènement va permettre de l'invoquer dès que l'évènement -est levé. Il faut donc être attentif car cette gestion d'évènement peu -modifier le comportement du composant de manière imprévisible. +Les événements d'un composant sont des propriétés spéciales qui prennent pour +valeur des méthodes (appelées `gestionnaires d'événement`). Le fait d'attacher +(assigner ou affecter) une méthode à un événement va permettre d'invoquer automatiquement +la méthode dès que l'événement est levé. Par conséquent, le comportement d'un composant +peut être modifié d'une manière qui ne peut parfois pas être prévue pendant le développement et +la mise au point du composant. -Un évènement est défini par une méthode dont le nom commence par `on`. -Comme pour les propriétés définies par getter/setter, les noms des -évènements ne sont pas sensible à la casse. Le code suivant défini -un évènement `onClicked`: + +Un événement est défini par une méthode dont le nom commence par `on`. +Tout comme pour les propriétés définies par des getter/setter, les noms des +événements ne sont pas sensibles à la casse. Le code suivant définit +un événement `onClicked`: ~~~ [php] @@ -74,35 +77,35 @@ public function onClicked($event) } ~~~ -ou `$event` est une instance de [CEvent] ou de l'une de ses classes filles. +où `$event` est une instance de [CEvent] ou de l'une de ses classes filles. -Une méthode est attachée à un évènement de la façon suivante: +Une méthode est attachée à un événement de la façon suivante: ~~~ [php] $component->onClicked=$callback; ~~~ -ou `$callback` est une fonction de rappel PHP valide. Ce peut être une fonction +où `$callback` est une fonction de rappel PHP valide. Ce peut être une fonction globale ou une méthode de classe. Dans ce cas (méthode de classe), la fonction de rappel doit être donnée via un array: `array($object,'methodName')`. -La signature d'un gestionnaire d'évènement doit être: +La signature d'un gestionnaire d'événement doit être: ~~~ [php] function methodName($event) { - ...... + ...... } ~~~ -ou le paramètre `$event` décrit l'évènement (il provient de l'appel de +où le paramètre `$event` décrit l'événement (il provient de l'appel de `raiseEvent()`). Le paramètre `$event` doit être une instance de [CEvent] ou de l'une de ses classes dérivées. A minima, il doit permettre de -savoir qui à levé l'évènement. +savoir qui a levé l'événement. -A partir de la version 1.0.10, un gestionnaire d'évènement peut être une fonction anonyme (ou lambda function) +A partir de la version 1.0.10, un gestionnaire d'événement peut être une fonction anonyme (ou une fonction lambda) ce qui est supporté depuis PHP 5.3. Par exemple, ~~~ @@ -113,12 +116,12 @@ $component->onClicked=function($event) { ~~~ -Désormais, si l'on appelle `onClicked()`, l'évènement `onClicked` va être -levé (a l'intérieur de `onClicked()`), et le gestionnaire d'évènement +Désormais, si l'on appelle `onClicked()`, l'événement `onClicked` va être +levé (à l'intérieur de `onClicked()`), et le gestionnaire d'événement associé va être invoqué automatiquement. -Un évènement peut être attaché à plusieurs gestionnaires. Lorsqu'un évènement -est levé, les gestionnaires seront invoqués dans l'ordre ou ils ont été attachés. +Un événement peut être attaché à plusieurs gestionnaires. Lorsqu'un événement +est levé, les gestionnaires seront invoqués dans l'ordre dans lequel ils ont été attachés. Si un gestionnaire doit interdire l'invocation des autres gestionnaires, il lui suffit de mettre la propriété [$event->handled|CEvent::handled] à true. @@ -129,8 +132,8 @@ Comportement d'un composant A partir de la version 1.0.2, le support des [mixin](http://en.wikipedia.org/wiki/Mixin) a été ajouté pour permettre d'attacher un composant à plusieurs comportements. Un *comportement* (behavior) est un objet dont les méthodes peuvent être -'hérités' par les composants qui y sont attachés. L'idée étant de collecter -des fonctionnalités au lieu de spécialiser les composants (i.e., notion +'héritées' par les composants qui y sont attachés. L'idée étant de collecter +des fonctionnalités au lieu de spécialiser les composants (i.e. notion d'héritage classique). Un composant peut donc être attaché à plusieurs composants pour simuler 'l'héritage multiple'. @@ -143,7 +146,7 @@ fonctionnalités spécifiques à la gestion des modèles. Pour pouvoir utiliser un comportement, il doit tout d'abord être attaché à un composant en appelant la méthode [attach()|IBehavior::attach]. Ensuite, -le comportement peut appelé via le composant comme ceci: +le comportement peut être appelé via le composant comme ceci: ~~~ [php] @@ -155,7 +158,7 @@ $component->test(); Un comportement attaché à un composant peut être accédé comme une propriété classique. Par exemple, si le comportement `tree` est attaché au composant -component, on peut obtenir la référence du comportement en utilisant: +`component`, on peut obtenir la référence du comportement en utilisant: ~~~ [php] @@ -166,7 +169,7 @@ $behavior=$component->tree; Un comportement peut être temporairement désactivé pour que ses méthodes ne puissent être appelées par le composant. -Par exemple, +Par exemple: ~~~ [php] @@ -182,16 +185,16 @@ Il est possible que deux comportements attachés au même composant aient des m avec un même nom. Dans ce cas, la méthode appartement au comportement qui a été attaché en premier aura la précédence. -Lorsqu'ils sont utilisés avec les [évènements](#component-event), les comportements -peuvent être extrèmement puissant. Un comportement, lorsqu'il est affecté à un -composant, peut attacher certaines de ses méthodes à des évènements du composant. +Lorsqu'ils sont utilisés avec les [événements](#component-event), les comportements +peuvent être extrêmement puissants. Un comportement, lorsqu'il est affecté à un +composant, peut attacher certaines de ses méthodes à des événements du composant. Cela permet à un comportement d'observer et/ou de changer le cycle d'exécution du composant. Depuis la version 1.1.0, les propriétés d'un `comportement` sont accessibles directement depuis le composant auquel il est rattaché. Les propriétés intègrent à la fois les variables publiques -et les propriétés du comportement définies par getter/setter. Par exemple, si un comportement +et les propriétés du comportement définies par les getter/setter. Par exemple, si un comportement à une propriété `xyz` et que ce comportement est attaché à un composant `$a`. Alors il devient possible d'utiliser l'expression `$a->xyz` pour accéder à la propriété du comportement. -
$Id: basics.component.txt 1474 $
\ No newline at end of file +
$Id: basics.component.txt 1476 $
\ No newline at end of file diff --git a/docs/guide/fr/basics.controller.txt b/docs/guide/fr/basics.controller.txt index 98a68b896a..6c0ce0f15f 100644 --- a/docs/guide/fr/basics.controller.txt +++ b/docs/guide/fr/basics.controller.txt @@ -3,17 +3,17 @@ Contrôleur Un contrôleur est une instance de [CController] ou d'une classe qui en hérite. Il est créé par l'objet application lorsque l'utilisateur en fait la demande. Quand un contrôleur -s'exécute, il effectue l'action demandée, ce qui porte habituellement -dans les modèles nécessaires et rend une vue appropriée. Un `action`, dans sa forme la plus simple, est +s'exécute, il effectue l'action demandée, ce qui habituellement appelle +les modèles nécessaires et affiche la vue appropriée. Une `action`, dans sa forme la plus simple, est juste une méthode de la classe contrôleur dont le nom commence par `action`. -Un contrôleur a une action par défaut. Lorsque la demande de l'utilisateur ne spécifie pas -l'action à exécuter, l'action par défaut sera exécutée. Par défaut, -l'action par défaut est nommé comme `index`. Elle peut être modifiée par le réglage de +Un contrôleur a une action par défaut. Lorsque la requête de l'utilisateur ne spécifie pas +d'action à exécuter, l'action par défaut sera exécutée. Par défaut, +l'action par défaut est nommée `index`. Elle peut être modifiée en spécifiant la variable d'instance publique, [CController::defaultAction]. -Le code suivant définit un contrôleur `site`, une action `index` (la valeur par défaut -action), et une action `contact` : +Le code suivant définit un contrôleur `site`, une action `index` (l'action par défaut), et +une action `contact` : ~~~ [php] @@ -36,14 +36,14 @@ Route ----- Les contrôleurs et les actions sont identifiés par des ID. Un ID de contrôleur est -au format `path/to/xyz`, ce qui correspond au fichier de classe contrôleur -`protected/controllers/path/to/XyzController.php`, où le jeton `xyz` +au format `path/to/xyz`, ce qui correspond au fichier de classe du contrôleur +`protected/controllers/path/to/XyzController.php`, et où le jeton `xyz` doit être remplacé par les noms réels; par exemple `post` correspond à -`protected/controllers/PostController.php`. L'ID d'action est le nom de la méthode -d'action sans `action` préfixe. Par exemple, si une classe de contrôleur +`protected/controllers/PostController.php`. L'ID de l'action est le nom de la méthode +d'action sans le préfixe `action`. Par exemple, si une classe de contrôleur contient une méthode nommée `actionEdit`, l'ID de l'action correspondante serait `edit`. -Les utilisateurs demandent un contrôleur spécifique et d'action en termes de route. +Les utilisateurs nécessitent un contrôleur et une action spécifiques en termes de route. Une route est formée par la concaténation d'un ID de contrôleur et un ID d'action, séparés par une barre oblique. Par exemple, la route `post/edit` réfère à `PostController` et son action `edit`. Par défaut, l'URL `http://hostname/index.php?r=post/edit` @@ -139,18 +139,18 @@ organiser le code pour les contrôleurs : ~~~ protected/ - controllers/ - PostController.php - UserController.php - post/ - CreateAction.php - ReadAction.php - UpdateAction.php - user/ - CreateAction.php - ListAction.php - ProfileAction.php - UpdateAction.php + controllers/ + PostController.php + UserController.php + post/ + CreateAction.php + ReadAction.php + UpdateAction.php + user/ + CreateAction.php + ListAction.php + ProfileAction.php + UpdateAction.php ~~~ ### Paramètre Action reliure @@ -185,7 +185,7 @@ class PostController extends CController else $language='en'; - // ... Code plaisir commence ici ... + // ... Le code amusant commence ici ... } } ~~~ @@ -200,20 +200,20 @@ class PostController extends CController { $category=(int)$category; - // ... Code plaisir commence ici ... + // ... Le code amusant commence ici ... } } ~~~ -Notez que nous avons ajouter deux paramètres à la méthode d'action `actionCreate`. -Le nom de ces paramètres doivent être exactement les mêmes que ceux que -nous attendons de `$_GET`. La paramètre de `$language` prend une valeur par défaut `en` -au cas où la demande ne comprend pas un tel paramètre. Parce que `$category` -n'a pas de valeur par défaut, si la demande ne comporte pas de paramètre `category`, -un [CHttpException](error code 400) sera lancé automatiquement. +Notez que nous avons ajouté deux paramètres à la méthode d'action `actionCreate`. +Les noms de ces paramètres doivent être exactement les mêmes que ceux que attendus +du `$_GET`. La paramètre `$language` prend une valeur par défaut `en` +au cas où la requête ne contient pas ce paramètre. Comme `$category` n'a pas de +valeur par défaut, si la requête ne comporte pas de paramètre `category`, +une [CHttpException](error code 400) sera automatiquement levée. -Depuis la version 1.1.5, Yii supporte également la détection de type tableau pour les paramètres de l'action. -Cela se fait par type PHP allusion en utilisant la syntaxe comme suit : +Depuis la version 1.1.5, Yii supporte également la détection du type tableau pour les paramètres de l'action. +Cela se fait par allusion au type PHP en utilisant une syntaxe comme suit: ~~~ [php] @@ -227,17 +227,17 @@ class PostController extends CController ~~~ Autrement dit, nous ajoutons le mot-clé `array` en face de `$categories` dans la déclaration -de paramètre de méthode. Ce faisant, si `$_GET['categories']` est une chaîne simple, il sera +du paramètre de la méthode. Ce faisant, si `$_GET['categories']` est une chaîne simple, il sera converti en un tableau constitué de cette chaîne. -> Remarque : Si un paramètre est déclaré sans `array` soupçon type, cela signifie que le paramètre -> doit être un scalaire (ie, pas un tableau). Dans ce cas, en passant un paramètre de tableau via +> Remarque : Si un paramètre est déclaré sans l'allusion au type `array`, cela signifie que le paramètre +> doit être un scalaire (ie, pas un tableau). Dans ce cas, passer un paramètre de type tableau via > `$_GET` provoquerait une exception HTTP. -Depuis la version 1.1.7, la liaison des paramètres automatique fonctionne également pour -la classe de base des actions. Lorsque le `run()` méthode d'une classe d'action est défini -avec certains paramètres, ils seront remplis avec les correspondants désignés -valeurs de paramètre de requête. Par exemple, +Depuis la version 1.1.7, la liaison automatique des paramètres fonctionne également pour +les actions basées sur des classe. Lorsque la méthode `run()` d'une classe d'action est définie +avec des paramètres, ceux-ci seront renseignés/initialisés avec les valeurs des paramètres de requête correspondants. +Par exemple: ~~~ [php] @@ -245,7 +245,7 @@ class UpdateAction extends CAction { public function run($id) { - // $id will be populated with $_GET['id'] + // $id sera renseigné/initialisé avec $_GET['id'] } } ~~~ @@ -254,34 +254,34 @@ class UpdateAction extends CAction Filtre ------ -Le filtre est un morceau de code qui est configuré pour être exécuté avant et/ou -après une action de contrôleur exécute. Par exemple, un filtre de contrôle d'accès +Un filtre est un morceau de code qui est configuré pour être exécuté avant et/ou +après l'exécution de l'action du contrôleur. Par exemple, un filtre de contrôle d'accès peut être exécuté afin de s'assurer que l'utilisateur est authentifié avant d'exécuter -l'action demandée; performances d'un filtre peut être utilisé pour mesurer le temps +l'action demandée; Un filtre de performances peut être utilisé pour mesurer le temps passé à l'exécution de l'action. Une action peut avoir plusieurs filtres. Les filtres sont exécutés dans l'ordre -où ils apparaissent dans la liste de filtres. Un filtre peut empêcher l'exécution -de l'action et le reste des filtres non exécutés. +où ils apparaissent dans la liste des filtres. Un filtre peut empêcher l'exécution +de l'action et du reste des filtres non encore exécutés. -Un filtre peut être définie comme une méthode de la classe contrôleur. Le nom de la méthode doit +Un filtre peut être défini comme une méthode de la classe contrôleur. Le nom de la méthode doit commencer par `filter`. Par exemple, une méthode nommée `filterAccessControl` -définit un filtre nommé `accessControl`. La méthode de filtrage doit porter -la signature à droite : +définit un filtre nommé `accessControl`. La méthode de filtrage doit comporter +une signature précise: ~~~ [php] public function filterAccessControl($filterChain) { - // Appel de l'exécution $filterChain->run() pour continuer filtre et l'action + // Appel $filterChain->run() pour continuer l'exécution du filtre et l'action } ~~~ où `$filterChain` est une instance de [CFilterChain] qui représente -la liste des filtres associés à l'action demandée. L'intérieur d'une méthode de filtrage, -on peut appeler `$filterChain->run()` de continuer à filtre et exécution de l'action. +la liste des filtres associés à l'action demandée. A l'intérieur d'une méthode de filtrage, +on peut appeler `$filterChain->run()` pour continuer à filtrer et exécuter l'action. -Un filtre peut également être une instance de [CFilter] ou sa classe enfant. +Un filtre peut également être une instance de [CFilter] ou de sa classe enfant. Le code suivant définit une nouvelle classe de filtre: ~~~ @@ -290,20 +290,20 @@ class PerformanceFilter extends CFilter { protected function preFilter($filterChain) { - // Logique étant appliquée avant que l'action est exécutée + // La logique à appliquer avant que l'action soit exécutée return true; // false si l'action ne doit pas être exécuté } protected function postFilter($filterChain) { - // Logique étant appliqué après l'action est exécutée + // La logique à appliquer après que l'action soit exécutée } } ~~~ -Pour appliquer des filtres à des actions, nous avons besoin de surcharger les -`CController::filters()` méthode. La méthode doit retourner un tableau -des configurations de filtres. Par exemple, +Pour appliquer des filtres à des actions, nous avons besoin de surcharger +la méthode `CController::filters()`. La méthode doit retourner un tableau +de configuration des filtres. Par exemple: ~~~ [php] @@ -324,19 +324,19 @@ class PostController extends CController ~~~ Le code ci-dessus spécifie deux filtres: `postOnly` et `PerformanceFilter`. -Le `postOnly` filtre est fondée sur une méthode (la méthode de filtre correspondant -est défini dans [CController] déjà), tandis que le `PerformanceFilter` filtre est basé -sur les objets. Le chemin alias `application.filters.PerformanceFilter` -spécifie que le fichier de classe filtre est `protected/filters/PerformanceFilter`. +Le filtre `postOnly` est basé sur une méthode (la méthode de filtre correspondante +est définie dans [CController] déjà), tandis que le filtre `PerformanceFilter` est basé +sur un objet. L'alias de chemin `application.filters.PerformanceFilter` +spécifie que le fichier de classe du filtre est `protected/filters/PerformanceFilter`. Nous utilisons un tableau de configurer `PerformanceFilter` de sorte qu'il peut être -utilisé pour initialiser les valeurs des propriétés de l'objet filtre. Voici la `unit` -propriété de `PerformanceFilter` sera initialisé comme `second`. - -En utilisant le plus et les moins opérateurs, on peut spécifier quelles actions -le filtre doit et ne doit pas être appliqué. Dans ce qui précède, le `postOnly` -filtre sera appliqué pour les `edit` et de `create` actions, tandis que -`PerformanceFilter` filtre sera appliqué à toutes les actions SAUF `edit` et -`create`. Si aucun avantage ni moins apparaît dans la configuration du filtre, +utilisé pour initialiser les valeurs des propriétés de l'objet filtre. Dans cet exemple, +la propriété `unit` de `PerformanceFilter` sera initialisée à `second`. + +En utilisant les opérateurs plus et moins, il est possible de spécifier pour quelles actions +le filtre doit ou ne doit pas être appliqué. Dans ce qui précède, le filtre `postOnly` +sera appliqué pour les actions `edit` et `create`, tandis que le filtre `PerformanceFilter` +sera appliqué à toutes les actions SAUF `edit` et `create`. +Si aucun des signes plus ou moins apparaît dans la configuration du filtre, le filtre sera appliqué à toutes les actions.
$Id$
diff --git a/docs/guide/fr/basics.convention.txt b/docs/guide/fr/basics.convention.txt index a8eb22f8e6..59433d2d2f 100644 --- a/docs/guide/fr/basics.convention.txt +++ b/docs/guide/fr/basics.convention.txt @@ -33,7 +33,7 @@ Cette fonctionnalité est décrite en détail dans le chapitre [Gestion des URLs Programmation / Code -------------------- -Yii recommande d'écrire les variables, functions et classes en camel case. Cela +Yii recommande d'écrire les variables, fonctions et classes en camel case. Cela signifie qu'il faut mettre en majuscule la première lettre de chaque mot puis fusionner le tout sans espace. Dans le cas des noms de variables et de fonctions, la première lettre doit être @@ -42,7 +42,7 @@ mise en minuscule pour pouvoir les différencier des noms de classes (e.g. `$bas il est recommandé de préfixer leur nom d'une underscore (e.g. `$_actionList`). -Sachant que la notion de namespace n'est pas supporté par les versions de PHP +Sachant que la notion de namespace n'est pas supportée par les versions de PHP antérieures à la 5.3.0, il est recommandé de nommer les classes de manière unique afin d'éviter tout conflit avec les classes tierces. C'est pour cette raison que toutes les classes du framework sont préfixées de la lettre "C". @@ -100,8 +100,8 @@ Par défaut, Yii s'appuie sur plusieurs répertoires. Chacun peut - `WebRoot/protected`: C'est le [dossier de base de l'application](/doc/guide/basics.application#application-base-directory) qui contient tous les éléments sensibles (PHP et données). Yii dispose d'un -raccourcis par défaut `application` associé à ce chemin. Tout accès à ce dossier, -ainsi qu'à ce qu'il contient doit être interdit aux utilisateurs web. Ce chemin +raccourcis par défaut `application` associé à ce chemin. Tout accès à ce dossier +ainsi qu'à son contenu doit être interdit aux utilisateurs web. Ce chemin peut être modifié via la propriété [CWebApplication::basePath]. - `WebRoot/protected/runtime`: ce dossier contient les fichiers @@ -135,32 +135,28 @@ vues systèmes. Les vues systèmes sont des gabarits permettant l'affichage des exceptions et des erreurs. Ce chemin peut être modifié via la propriété [CWebApplication::systemViewPath]. - - `WebRoot/assets`: ce dossier contient les assets publiés. Un asset est un fichier -privé qui peut être publié et donc rendu accessible à l'utilisateur web. -Le processus web doit pouvoir y accéder en écriture. Ce chemin peut être modifié via -la propriété [CAssetManager::basePath]. + - `WebRoot/assets`: ce dossier contient les assets publiés. Un asset est un fichier privé qui peut être publié et donc rendu accessible à l'utilisateur web. +Le processus web doit pouvoir y accéder en écriture. +Ce chemin peut être modifié via la propriété [CAssetManager::basePath]. - `WebRoot/themes`: ce dossier contient les divers thèmes qui peuvent être -utilisés par l'application. Chaque sous dossier correspond à un et un seul thème dont -le nom est le nom du dossier. Ce chemin peut être modifié via +utilisés par l'application. Chaque sous dossier correspond à un et un seul thème dont le nom est le nom du dossier. Ce chemin peut être modifié via la propriété [CThemeManager::basePath]. Base de données --------------- La plupart des applications Web utilisent une base de données. En guise de bonne pratique, -nous proposons les conventions de nommage suivantes (qui ne sont pas nécessaire au bon +nous proposons les conventions de nommage suivantes (qui ne sont pas nécessaires au bon fonctionnement de Yii): - - Les noms de tables et de colonnes doivent être en minuscules. + - Les noms des tables et des colonnes doivent être en minuscules. - Les mots au sein d'un nom doivent être séparés par des underscores (ex: `product_order`). - - Les noms de tables peuvent être au singulier ou au pluriel, mais pas les deux. + - Les noms des tables peuvent être au singulier ou au pluriel, mais pas les deux. Pour simplifier, nous recommandons d'utiliser des noms singuliers. - - Les noms des tables peuvent être préfixés d'une chaine commune telle que `tbl_`. Cela peut -être utile lorsque des tables de plusieurs applications distinctes doivent coexister au sein -de la même base de données. + - Les noms des tables peuvent être préfixés d'une chaîne commune à toutes les tables. Par exemple `tbly1_`. Typiquement, cela est utile pour éviter des conflits de noms lorsque la base de données est partagée entre plusieurs applications et que des tables distinctes doivent donc coexister au sein de cette même base de données. -
$Id: basics.convention.txt 1906 $
+
$Id: basics.convention.txt 1907 $
diff --git a/docs/guide/fr/basics.model.txt b/docs/guide/fr/basics.model.txt index c97309856c..2211d39370 100644 --- a/docs/guide/fr/basics.model.txt +++ b/docs/guide/fr/basics.model.txt @@ -5,7 +5,7 @@ Un modèle est une instance de [CModel] ou d'une classe qui en hérite. Les modèles sont utilisés pour stocker des données ainsi que les règles métier associées. Un modèle représente un objet simple. Ce peut être une ligne d'une -table de la base de données ou les données d'un formulaire. Chaque +table de la base de données ou les données d'un formulaire. Chaque champs de l'objet est représenté par un attribut du modèle. L'attribut a un label et peut être validé par un jeu de règles métiers. @@ -13,9 +13,9 @@ Yii implémente deux sortes de modèles: les formulaire et l'active record. Ces deux modèles étendent la même classe de base : [CModel]. Un modèle de formulaire est une instance de [CFormModel]. Ce type de modèle -est utilisé pour conserver les données entrées par un utilisateur. Ces +est utilisé pour conserver les données entrées par un utilisateur. Ces données sont classiquement collectées, utilisées puis jetées. Par exemple, -sur une page d'authentification, il est possible d'utiliser une modèle de +sur une page d'authentification, il est possible d'utiliser une modèle de formulaire qui représente les informations fournies par l'utilisateur telles que son identifiant et son mot de passe. Pour plus de détail, veuillez vous référer à la page [Travailler avec les formulaires](/doc/guide/form.model) @@ -27,4 +27,4 @@ hérite. Ces objets représentent une ligne d'une table de la base de données. colonne de la ligne est représenté par une propriété de l'objet AR. Pour plus de détail, veuillez vous référer à la page [Active Record](/doc/guide/database.ar). -
$Id: basics.model.txt 162$
+
$Id: basics.model.txt 162 2013-11-20$
diff --git a/docs/guide/fr/basics.module.txt b/docs/guide/fr/basics.module.txt index 9bdbe15a63..1fa5a5ba21 100644 --- a/docs/guide/fr/basics.module.txt +++ b/docs/guide/fr/basics.module.txt @@ -1,29 +1,27 @@ Module ====== -> Note: Le support des modules est disponible depuis la version 1.0.3. - Un module est un bout de logiciel autonome qui comporte des [modèles](/doc/guide/basics.model), des [vues](/doc/guide/basics.view), -des [contrôleurs](/doc/guide/basics.controller) et autres composants. -Sous plusieurs aspects, un module est assez similaire à une [application](/doc/guide/basics.application). +des [contrôleurs](/doc/guide/basics.controller) et autres composants nécessaires à son bon fonctionnement. +Sous plusieurs aspects, un module ressemble à une [application](/doc/guide/basics.application). La principale différence est qu'un module ne peut être déployé seul et doit absolument être inclus dans une application. Les utilisateurs peuvent accéder aux contrôleurs d'un module comme ils le font avec les contrôleurs de l'application. -Les modules sont utiles dans divers cas. -Pour une application conséquente, il est possible de la diviser en +Les modules sont utiles dans plusieurs cas. +Pour une application à grande échelle, il est possible de la diviser en plusieurs modules, chacun étant développé et maintenu indépendament. -Des fonctionnalités génériques telles que la gestion des utilisateurs, des commentaires +Des fonctionnalités génériques partagées telles que la gestion des utilisateurs ou des commentaires peuvent être déployées sous la forme de modules et ainsi être réutilisées simplement dans d'autres projets. -Créer Un Module +Créer un Module --------------- -Un module est conçu à l'intérieur d'un dossier. Ce dossier défini son [ID|CWebModule::id] unique. +Un module est organisé à l'intérieur d'un dossier. C'est le nom de ce dossier qui définit son identifiant unique [ID|CWebModule::id]. La structure d'un module est similaire à celle du [dossier de base de l'application](/doc/guide/basics.application#application-base-directory). Ci-dessous, la structure du module `forum`: @@ -32,27 +30,29 @@ Ci-dessous, la structure du module `forum`: forum/ ForumModule.php La classe du module components/ Composants réutilisables - views/ Vues widget + views/ Vues widget controllers/ Contrôleurs - DefaultController.php Contrôleur par défaut + DefaultController.php Contrôleur par défaut extensions/ Extensions tierces models/ Modèles views/ Vues et Layouts - layouts/ Layouts - default/ Vues du contrôleur par défaut - index.php La vue index + layouts/ Layouts + default/ Vues du contrôleur par défaut + index.php La vue index ~~~ Un module doit avoir une classe qui étends [CWebModule]. Le nom de cette classe est défini par l'expression `ucfirst($id).'Module'`, -ou `$id` aorrespond à l'ID du module (ou au nom du dossier du module). -La classes du module est le noyau central qui permet de gérer et sauvegarder -toutes les informations nécessaires au bon foncitonnement du code. +où `$id` correspond à l'ID du module (ou au nom du dossier du module). +La classe du module est le noyau central qui gére et sauvegarde toutes les +informations partagées entre tout le code du module. Par exemple, il est possible d'utiliser [CWebModule::params] pour sauvegarder les paramètres, et d'utiliser [CWebModule::components] pour partager les [composants applicatifs](/doc/guide/basics.application#application-component) au niveau du module. -> Astuce|Tip: Il est possible d'utiliser l'outil `yiic` pour créer le squelette d'un module. Par exemple, pour créer le module `forum`, il faut exécuter la commande CLI suivante: +> Astuce: Il est possible d'utiliser le générateur de module disponible dans `Gii` pour créer un squelette de module. + +> Astuce: Il est possible d'utiliser l'outil `yiic` pour créer le squelette d'un module. Par exemple, pour créer le module `forum`, il faut exécuter la commande CLI suivante: > > ~~~ > % cd WebRoot/testdrive @@ -85,7 +85,7 @@ return array( Un module peut aussi être configuré. L'usage est très similaire à la configuration des [composants d'application](/doc/guide/basics.application#application-component). Par exemple, le module `forum` pourrait avoir une propriété nommée -`postPerPage` au sein de sa class qui pourrait être configurée dans la +`postPerPage` au sein de sa classe et qui pourrait être configurée dans la [configuration de l'application](/doc/guide/basics.application#application-configuration) comme suit: ~~~ @@ -93,15 +93,15 @@ Par exemple, le module `forum` pourrait avoir une propriété nommée return array( ...... 'modules'=>array( - 'forum'=>array( - 'postPerPage'=>20, - ), + 'forum'=>array( + 'postPerPage'=>20, + ), ), ...... ); ~~~ -L'instance d'un module peut être accédé via la propriété [module|CController::module] p +L'instance d'un module peut être accédée via la propriété [module|CController::module] p du contrôleur courant. Au travers de l'instance du module, il est possible d'accéder aux informations qui sont partagées au niveau du module. Par exemple, au lieu d'accéder à `postPerPage`, il est possible d'utiliser l'expression suivante: @@ -109,11 +109,11 @@ au lieu d'accéder à `postPerPage`, il est possible d'utiliser l'expression sui ~~~ [php] $postPerPage=Yii::app()->controller->module->postPerPage; -// ou $this référence l'instance du contrôleur +// où $this référence l'instance du contrôleur // $postPerPage=$this->module->postPerPage; ~~~ -L'action d'un contrôleur d'un module peut être accédé en utilisant la [route](/doc/guide/basics.controller#route) `moduleID/controllerID/actionID`. Par exemple, en assumant que le module `forum` a un contrôleur nommé `PostController`, il est possible d'utiliser la [route](/doc/guide/basics.controller#route) `forum/post/create` pour référence l'action `create` au sein du contrôleur. L'URL correspondant à cette route serait `http://www.example.com/index.php?r=forum/post/create`. +L'action d'un contrôleur d'un module peut être accédée en utilisant la [route](/doc/guide/basics.controller#route) `moduleID/controllerID/actionID`. Par exemple, en assumant que le module `forum` a un contrôleur nommé `PostController`, il est possible d'utiliser la [route](/doc/guide/basics.controller#route) `forum/post/create` pour référencer l'action `create` au sein du contrôleur. L'URL correspondant à cette route serait `http://www.exemple.com/index.php?r=forum/post/create`. > Astuce|Tip: Si un contrôleur et dans un sous-dossier de `controllers`, il est possible d'utiliser le format de [route](/doc/guide/basics.controller#route) ci-dessus. Par exemple, si `PostController` est sous `forum/controllers/admin`, il est possible de référence l'action `create` en utilisant `forum/admin/post/create`. diff --git a/docs/guide/fr/basics.mvc.txt b/docs/guide/fr/basics.mvc.txt index a7a0a47a93..e544fb4a9d 100644 --- a/docs/guide/fr/basics.mvc.txt +++ b/docs/guide/fr/basics.mvc.txt @@ -1,55 +1,50 @@ Modèle-Vue-Contrôleur (MVC) =========================== -Yii implémente le modèle modèle-vue-contrôleur (MVC) de conception, qui est -largement adoptée dans la programmation Web. MVC a pour but de séparer la logique +Yii implémente le modèle de conception modèle-vue-contrôleur (MVC), qui est +largement adopté dans la programmation Web. MVC a pour but de séparer la logique métier des considérations d'interface utilisateur, de sorte que les développeurs peuvent plus facilement changer chaque partie sans affecter l'autre. Dans MVC, le modèle représente l'information (les données) et les règles de gestion, la vue contient des éléments de l'interface utilisateur tels que les entrées sous forme de texte, et le contrôleur gère la communication entre le modèle et la vue. -D'ailleurs la mise en oeuvre MVC, Yii introduit également une première commande, -appelée `Application`, qui encapsule le contexte d'exécution pour le traitement +Outre la mise en oeuvre de MVC, Yii introduit également un contrôleur frontal, +appelé `Application`, qui encapsule le contexte d'exécution pour le traitement d'une requête. Application recueille des informations sur une requête de l'utilisateur, -puis il envoie à un contrôleur approprié pour une manipulation ultérieure. +puis il l'envoie à un contrôleur approprié pour traitement ultérieur. Le schéma suivant montre la structure statique d'une application Yii: -![Structure statique d'application Yii](structure.png) +![Structure statique d'une Application Yii](structure.png) Un flux de travail typique -------------------------- Le schéma suivant montre un flux de travail typique d'une application Yii quand -il traite une demande de l'utilisateur : +elle traite une requête d'un utilisateur: ![Déroulement typique d'une application Yii](flow.png) - 1. Un utilisateur envoie une requête au lien `http://www.example.com/index.php?r=post/show&id=1` -et le serveur Web traite la requête en exécutant le script bootstrap `index.php`. - 2. Le script d'amorçage crée une [Application](/doc/guide/basics.application) -exemple et l'exécute. - 3. L'application obtient des informations détaillées sur demande de l'utilisateur à partir -un [composant d'application](/doc/guide/basics.application#application-component) + 1. Une requête utilisateur est envoyée à l'adresse `http://www.exemple.com/index.php?r=post/show&id=1` +et le serveur Web traite la requête en exécutant le script d'amorçage (bootstrap) `index.php`. + 2. Le script d'amorçage crée une instance d'[Application](/doc/guide/basics.application) et l'exécute. + 3. L'Application obtient des informations détaillées sur la demande de l'utilisateur depuis le [composant d'application](/doc/guide/basics.application#application-component) nommé `request`. - 4. L'application détermine la demande [controller](/doc/guide/basics.controller) -et [action](/doc/guide/basics.controller#action) avec à l'aide -d'un composant d'application nommé `urlManager`. Pour cet exemple, le contrôleur + 4. L'application détermine le [controller](/doc/guide/basics.controller) demandé +et l'[action](/doc/guide/basics.controller#action) avec l'aide +d'un composant d'application nommé `urlManager`. Dans cet exemple, le contrôleur est `post`, qui se réfère à la classe `PostController`, et l'action est `show`, -dont réelle signification est déterminée par le contrôleur. - 5. L'application crée une instance du contrôleur demandé -en outre à traiter la demande de l'utilisateur. Le contrôleur détermine que l'action -`show` fait référence à une méthode nommée `actionShow` dans la classe contrôleur. il a ensuite -crée et exécute des filtres (par exemple l'accès de contrôle, analyse comparative) associé -avec cette action. L'action est exécutée si elle est autorisée par les filtres. - 6. L'action se lit un `Post` [model](/doc/guide/basics.model) dont l'ID est `1` à partir de la base de données. - 7. L'action rend un [view](/doc/guide/basics.view) nommé `show` avec le modèle `Post`. - 8. Le point de vue lit et affiche les attributs du modèle `Post`. - 9. La vue exécute une [widgets](/doc/guide/basics.view#widget). - 10. Le résultat rendu de la vue est intégré dans un [layout](/doc/guide/basics.view#layout). - 11. L'action complète la vue de rendu et affiche le résultat à l'utilisateur. - - -
$Id$
+dont la signification réelle est déterminée par le contrôleur. + 5. L'application crée une instance du contrôleur demandé pour traiter la demande de l'utilisateur. +Le contrôleur détermine que l'action `show` fait référence à une méthode nommée `actionShow` dans la classe contrôleur. Ensuite il crée et exécute les filtres (par exemple contrôle de l'accès, mesure des performances) associés à cette action. L'action est exécutée si elle est autorisée par les filtres. + 6. L'action lit en base de données un `Post` [model](/doc/guide/basics.model) dont l'ID est `1`. + 7. L'action effectue le rendu de la vue [view](/doc/guide/basics.view) nommée `show` avec le modèle `Post`. + 8. La vue lit et affiche les attributs du modèle `Post`. + 9. La vue exécute quelques [widgets](/doc/guide/basics.view#widget). + 10. Le résultat du rendu de la vue est intégré dans un gabarit [layout](/doc/guide/basics.view#layout). + 11. L'action complète le rendu de la vue et affiche le résultat à l'utilisateur. + + +
$Id: basics.mvc.txt 101 2013-11-20$
diff --git a/docs/guide/fr/basics.namespace.txt b/docs/guide/fr/basics.namespace.txt index 209ef03d62..4e21e7fd50 100644 --- a/docs/guide/fr/basics.namespace.txt +++ b/docs/guide/fr/basics.namespace.txt @@ -1,7 +1,7 @@ Alias et Espaces de noms ======================== -Yii utilise les alias de manière intensive. Un alias défini l'accès à un dossier +Yii utilise les alias de manière intensive. Un alias définit l'accès à un dossier ou à un fichier. Les alias s'écrivent en utilisant une notation pointée similaire au format largement utilisé pour déclarer des espaces de nom. @@ -9,7 +9,7 @@ largement utilisé pour déclarer des espaces de nom. AliasRacine.chemin.vers.la.cible ~~~ -ou `AliasRacine` est un alias vers un dossier existant. Il est possible de +où `AliasRacine` est un alias vers un dossier existant. Il est possible de définir de nouveaux alias en appelant [YiiBase::setPathOfAlias()]. Pour faciliter les choses, dles alias suivant sont prédéfinis par Yii : @@ -44,10 +44,11 @@ De la même manière, importer un alias plusieurs fois est plus rapide que d'utiliser les directives `include_once` et `require_once`. > Tip|Astuce: Il n'est pas nécessaire d'importer ou d'inclure les classes définies par le -framework Yii. Tous les classes du core étant pré-importées. +framework Yii. Toutes les classes du noyau (core) étant pré-importées (mais instanciées uniquement +à la demande). La syntaxe suivante peut être utilisée pour importer automatiquement -l'intégralité des classes d'un dossier. +l'intégralité des classes d'un dossier: ~~~ [php] @@ -57,7 +58,7 @@ Yii::import('system.web.*'); Les alias, en dehors de l'[import|YiiBase::import], sont utilisés pour référencer des classes. Par exemple, un alias peut être passé à [Yii::createComponent()] pour créer une instance de la classe correspondante, -et ce, même si la classe n'a pas été incluse précédemment. +et ce, même si la classe n'a pas été inclue précédemment. Il est important de ne pas confondre alias et espace de noms. Un espace de noms permet de grouper de manière logique des classes @@ -74,4 +75,4 @@ définies par l'utilisateur. Il est donc très fortement recommandé de réserve la lettre 'C' aux classes du framework Yii et d'utiliser d'autres lettres pour les classes utilisateur. -
$Id: basics.namespace.txt 1400 $
\ No newline at end of file +
$Id: basics.namespace.txt 1400 2013-11-20 $
\ No newline at end of file diff --git a/docs/guide/fr/basics.view.txt b/docs/guide/fr/basics.view.txt index 18fbc98f97..be82c26151 100644 --- a/docs/guide/fr/basics.view.txt +++ b/docs/guide/fr/basics.view.txt @@ -3,16 +3,16 @@ Vue Une vue est un script PHP qui comprend principalement des éléments de l'interface utilisateur. Elle peut contenir du code PHP, mais il est fortement recommandé -de ne pas altérer le modèle de données et de garder le code de la vue le plus +de ne pas altérer le modèle de données et de garder le code de la vue le plus simple possible. Pour respecter le concept de séparation des couches "logique" -et "présentation", le code lié à la partie logique doit être placée soit dans +et "présentation", le code lié à la partie logique doit être placé soit dans le contrôleur soit dans le modèle et non pas dans la vue. Une vue a un nom qui permet d'identifier le fichier de la vue lors de l'opération de rendu. Le nom de la vue est identique au nom du fichier. Par exemple, -la vue `edit` fait réfèrence au fichier PHP `edit.php`. Pour effectuer +la vue `edit` fait référence au fichier PHP `edit.php`. Pour effectuer l'opération de rendu, il faut appeler [CController::render()] avec le -nom de la vue. Cette méthode va rechercher le fichier correspondant dans le +nom de la vue. Cette méthode va rechercher le fichier correspondant dans le dossier `protected/views/ControllerID`. Il est possible d'accéder à l'instance du contrôleur depuis la vue @@ -20,7 +20,7 @@ en utilisant `$this`. Il est ainsi possible de `récupérer` dans la vue n'importe quelle propriété du contrôleur en évaluant `$this->propertyName`. Il est aussi possible d'utiliser cette méthode pour `pousser` des données -dans la vue: +dans la vue: ~~~ [php] @@ -37,11 +37,11 @@ il est possible d'accéder directement dans la vue aux variables locales `$var1` Gabarit ------- -Le gabarit (Layout) est une vue spéciale qui est utilisée pour décorer les vues. +Le gabarit (Layout) est une vue spéciale qui est utilisée pour décorer les vues. Dans la plupart des cas, il contient des portions de l'interface utilisateur qui sont -communes à plusieurs vues. -Par exemple, un gabarit pourrait contenir l'entête et le pied de page. Le contenu de -la vue étant disposé entre les deux, +communes à plusieurs vues. +Par exemple, un gabarit pourrait contenir l'entête et le pied de page. Le contenu de +la vue étant disposé entre les deux. ~~~ [php] @@ -54,7 +54,7 @@ la variable `$content` contient le rendu de la vue. Le gabarit (Layout) est appliqué de manière implicite lors de l'appel à [render()|CController::render]. Par défaut, le script de la vue `protected/views/layouts/main.php` est utilisé en tant que -gabarit. Le nom peut être modifié en modifiant [CWebApplication::layout] ou +gabarit. Le nom peut être modifié en modifiant [CWebApplication::layout] ou [CController::layout]. Pour effectuer un rendu sans appliquer le gabarit, il suffit d'appeler la méthode [renderPartial()|CController::renderPartial] en lieu et place de [render()|CController::render]. @@ -64,7 +64,7 @@ Widget Un widget est une instance de [CWidget] ou d'une classe dérivée. C'est un composant principalement destiné à la gestion de la présentation. La plupart du temps, les Widgets -sont embarqués dans une vue pour générer des éléments complexes et autonomes de l'interface +sont embarqués dans une vue afin d'y générer des éléments complexes et autonomes de l'interface utilisateur. Les widgets permettent d'augmenter le taux de ré-utilisabilité au sein des interfaces. @@ -84,13 +84,13 @@ ou widget('path.to.WidgetClass'); ?> ~~~ -La seconde version est utilisée lorsque le qidget ne nécessite pas de contenu. +La seconde version est utilisée lorsque le widget ne nécessite pas de contenu. -Les widgets peuvent être configuré pour adapter leurs comportements. Ceci -est effectué en modifiant les propriétés lors de l'appel à [CBaseController::beginWidget] -ou [CBaseController::widget]. Par exmple, lors de l'utilisation du widget +Les widgets peuvent être configurés pour adapter leurs comportements. Ceci +est effectué en modifiant les propriétés lors de l'appel à [CBaseController::beginWidget] +ou [CBaseController::widget]. Par exemple, lors de l'utilisation du widget [CMaskedTextField], nous voudrions pouvoir spécifier le masque à utiliser. -Cette adaptation est possible en passant un tableau qui contient les valeurs comme suit, +Cette adaptation est possible en passant un tableau qui contient les valeurs comme suit: ~~~ [php] @@ -110,12 +110,12 @@ class MyWidget extends CWidget { public function init() { - // this method is called by CController::beginWidget() + // cette méthode est appelée par CController::beginWidget() } public function run() { - // this method is called by CController::endWidget() + //cette méthode est appelée par CController::endWidget() } } ~~~ @@ -123,24 +123,24 @@ class MyWidget extends CWidget Comme pour un contrôleur, un widget peut avoir sa propre vue. Par défaut, les fichiers des vues des widgets sont stockés dans le sous-dossier `views` du dossier qui contient le fichier du widget. Ces vues peuvent être traitées -en appelant la méthode [CWidget::render()]. La principale différence entre les +en appelant la méthode [CWidget::render()]. La principale différence entre les contrôleurs et les vues est que les gabarits ne sont pas appliqués sur les widgets. Vue Système ----------- -Les vues systèmes sont des vues utilisés par Yii pour afficher les erreurs -ou des informations de log. Par exemple, lorsqu'un utilisateur appelle un +Les vues système sont des vues utilisées par Yii pour afficher les erreurs +ou des informations de log. Par exemple, lorsqu'un utilisateur appelle un contrôleur ou une action inexistente, Yii va lever une exception qui appelera -l'erreur. Yii affiche les exceptions en utilisant les vues système spécifiques. +l'erreur. Yii affiche les exceptions en utilisant des vues système spécifiques. -Le nommage des vues système doit respecter quelques règles. Des noms tel -que `errorXXX` se réfèrent à des vues qui permette d'afficher les erreurs [CHttpException]. -Par exempl, si [CHttpException] est levée avec un code erreur 404, la vue `error404` +Le nommage des vues système doit respecter quelques règles. Des noms tels +que `errorXXX` se réfèrent à des vues qui permettent d'afficher les erreurs [CHttpException]. +Par exemple, si [CHttpException] est levée avec un code erreur 404, la vue `error404` sera affichée. -Yii apporte un série de vues système par défaut qui sont stockés dans -`framework/views`. Elle peuvent être adaptés en créant des vues qui portent le -même nom dans le dossier `protected/views/system`. +Yii apporte une série de vues système par défaut et qui sont stockées dans +`framework/views`. Elles peuvent être adaptées en créant des vues qui portent le +même nom et stckées dans le dossier `protected/views/system`. -
$Id: basics.view.txt 416$
+
$Id: basics.view.txt 417 2013-11-20$
diff --git a/docs/guide/fr/caching.overview.txt b/docs/guide/fr/caching.overview.txt index b7c13af271..ef047a6e9e 100644 --- a/docs/guide/fr/caching.overview.txt +++ b/docs/guide/fr/caching.overview.txt @@ -1,13 +1,10 @@ La mise en cache ================ -La mise en cache est un moyen pas cher et efficace pour améliorer les performances d'une application -Web. En stockant les données relativement statiques dans le cache et en le signifiant à partir du -cache à la demande, nous économisons le temps nécessaire pour générer les données. +La mise en cache est un moyen bon marché et efficace d'améliorer les performances d'une application +Web. En stockant les données relativement statiques dans un cache et en les récupérant à la demande à partir de ce même cache, nous économisons le temps nécessaire pour générer ces données. -Utilisation du cache dans Yii concerne principalement la configuration et l'accès à -un composant de l'application cache. La configuration de l'application suivant spécifie -un composant de cache qui utilise memcache avec deux serveurs de cache. +Utiliser un cache dans Yii consiste principalement à configurer et accéder à un composant d'application cache. La configuration de l'application suivante spécifie un composant de cache qui utilise memcache avec deux serveurs de cache: ~~~ [php] @@ -29,54 +26,43 @@ array( Lorsque l'application est en cours d'exécution, le composant de cache peut être consulté via `Yii::app()->cache`. -Yii fournit des composants de cache différentes qui peuvent stocker des données mises en cache -dans différents médias. Par exemple, le composant [CMemCache] encapsule l'extension -PHP memcache et utilise la mémoire comme support de mémoire cache, le composant [CApcCache] -encapsule l'extension PHP APC et le composant [CDbCache] stocke les données en mémoire cache -dans la base de données de composants. Ce qui suit est un résumé des éléments de cache disponibles : +Yii fournit différents composants de cache qui peuvent stocker des données mises en cache dans différents médias. Par exemple, le composant [CMemCache] encapsule l'extension PHP memcache et utilise la mémoire comme support de mémoire cache, le composant [CApcCache] encapsule l'extension PHP APC et le composant [CDbCache] stocke les données à mettre en cache dans une base de données. Ce qui suit est un résumé des éléments de cache disponibles : - - [CMemCache]: PHP utilise [l'extention memcache](http://www.php.net/manual/en/book.memcache.php). + - [CMemCache]: utilise [l'extention PHP memcache] (http://www.php.net/manual/en/book.memcache.php). - - [CApcCache]: PHP utilise [l'extension APC](http://www.php.net/manual/en/book.apc.php). + - [CApcCache]: utilise [l'extension PHP APC] (http://www.php.net/manual/en/book.apc.php). - - [CXCache]: PHP utilise [l'extension XCache](http://xcache.lighttpd.net/). + - [CXCache]: utilise [l'extension PHP XCache](http://xcache.lighttpd.net/). - - [CEAcceleratorCache]: PHP utilise [l'extension EAccelerator](http://eaccelerator.net/). + - [CEAcceleratorCache]: utilise [l'extension PHP EAccelerator] (http://eaccelerator.net/). - - [CDbCache]: utilise une table de base de données pour stocker les données mises en cache. -Par défaut, il va créer et utiliser une base de données SQLite3 sous le répertoire d'exécution. -Vous pouvez spécifier explicitement une base de données pour qu'elle utilise en définissant sa +- [CRedisCache]: utilise [redis](http://redis.io/) valeur-clé de stockage comme support sous-jacent de mise en cache. + - [CDbCache]: utilise une table en base de données pour stocker les données mises en cache. +Par défaut, une base de données SQLite3 va être créée et utilisée dans le répertoire d'exécution. +Vous pouvez spécifier explicitement une base de données à utiliser en utilisant la propriété [connectionID|CDbCache::connectionID]. - - [CZendDataCache]: utilise [Zend Cache de Données](http://files.zend.com/help/Zend-Server-Community-Edition/data_cache_component.htm) - comme support de mise en cache sous-jacent. + - [CZendDataCache]: utilise [le cache de données Zend](http://files.zend.com/help/Zend-Server-Community-Edition/data_cache_component.htm) + comme support sous-jacent de mise en cache. + +- [CWinCache]: utilise l'extension [WinCache pour PHP](http://iis.net/downloads/microsoft/wincache-extension) + ([voir aussi](http://php.net/manual/en/book.wincache.php)). - [CFileCache]: utilise des fichiers pour stocker des données en mémoire cache. -Ceci est particulièrement adaptée pour mettre en cache gros morceau de données (telles que les pages). - - - [CDummyCache]: présente cache factice qui ne fait pas la mise en cache du tout. Le but -de ce composant est de simplifier le code qui a besoin de vérifier la disponibilité de cache. -Par exemple, au cours du développement ou si le serveur ne prend pas en charge la mémoire cache réelle, -nous pouvons utiliser ce composant cache. Quand un support mémoire cache réelle est activée, on peut passer -d'utiliser le composant de cache correspondant. Dans les deux cas, on peut utiliser le même code -`Yii::app()->cache->get($key)` pour tenter la récupération d'une partie des données sans se soucier -de ce que `Yii::app()->cache` peut-être `null`. - -> Acstuce : Parce que tous ces composants cache s'étendent de la même classe de base [CCache], -on peut passer d'utiliser un autre type de cache sans modifier -le code qui utilise le cache. - -La mise en cache peut être utilisé à différents niveaux. Au niveau le plus bas, nous utilisons -cache pour stocker un seul morceau de données, comme une variable, et nous appelons cela -*la mise en cache des données*. Au niveau suivant, on stocke dans la mémoire cache d'un fragment -de page qui est généré par une partie d'un script de vue. Et au plus haut niveau, nous -enregistrons une page entière dans le cache et le servir de la mémoire cache en fonction des besoins. +Ceci est particulièrement adapté pour mettre en cache de gros morceau de données (telles que des pages). + + - [CDummyCache]: est un cache factice qui ne fait pas la mise en cache du tout. Le but de ce composant est de simplifier le code qui a besoin de vérifier la disponibilité de cache. +Par exemple, au cours du développement ou si le serveur ne prend pas en charge la mémoire cache, nous pouvons utiliser ce composant cache. Quand un support de mémoire cache est disponible, on peut alors utiliser le composant de cache correspondant. Dans les deux cas, le code utilisé pour tenter de récupérer des données en cache est exactement le même +`Yii::app()->cache->get($key)` sans avoir à se soucier de ce que `Yii::app()->cache` est peut-être `null`. + +> Astuce: Parce que tous ces composants cache étendent la même classe de base [CCache], on peut passer d'un type de cache à un autre sans avoir à modifier le code qui utilise le cache. + +La mise en cache peut être utilisée à différents niveaux. Au niveau le plus bas, nous utilisons le cache pour stocker une seule données, comme une variable, et nous appelons cela *la mise en cache des données*. Au niveau suivant, on stocke dans la mémoire cache un fragment de page qui est généré par une partie d'un script de vue. Et au plus haut niveau, nous enregistrons une page entière dans le cache pour la resservir telle quelle de la mémoire cache en fonction des besoins. Dans les prochains paragraphes, nous élaborons comment utiliser le cache à ces niveaux. -> Remarque : Par définition, le cache est un support de stockage volatile. Il ne garantit pas -l'existence des données mises en cache même si elle n'expire pas. -Par conséquent, ne pas utiliser le cache comme un stockage persistant -(Par exemple : ne pas utiliser le cache pour stocker les données de session). +> Remarque: Par définition, un cache est un support de stockage volatile. Il ne garantit pas l'existence d'une donnée mise en cache même si elle n'expire pas. +Par conséquent, ne pas utiliser le cache comme un moyen de stockage persistant. +(Par exemple: ne pas utiliser le cache pour stocker les données de session). -
$Id$
\ No newline at end of file +
$Id$
diff --git a/docs/guide/fr/caching.page.txt b/docs/guide/fr/caching.page.txt index bbc744f0b2..406cf6b53c 100644 --- a/docs/guide/fr/caching.page.txt +++ b/docs/guide/fr/caching.page.txt @@ -1,24 +1,23 @@ Cache de page ============= -Le cache de page sert à mettre en cache l'intégralité d'une page. Le cache de page -peut être mis en oeuvre à différents niveaux. -Par exemple, en utilisant un header approprié, le navigateur peut mettre en -cache la page en cours pour une durée définie ou encore, l'application web peut -prendre en charge la mise en cache du contenu de la page. +Le cache de page sert à mettre en cache l'intégralité d'une page. Le cache de page +peut être mis en oeuvre de différentes façons et à différents niveaux. +Côté client, en utilisant un entête HTTP approprié, le navigateur peut mettre en +cache la page en cours pour une durée définie. +Côté serveur, l'application web peut prendre en charge la mise en cache du contenu de la page. Dans cette section, nous présenterons cette dernière méthode. -Le cache de page peut être considéré comme une extension du [cache par -fragments](/doc/guide/caching.fragment). Sachant que généralement -la génération d'une page fait intervenir un layout et une vue, il n'est +Le cache de page peut être considéré comme une extension du [cache par +fragments (EN)](/doc/guide/caching.fragment). Sachant que souvent la génération d'une page fait intervenir un layout et une vue, il n'est pas possible d'appeler les méthodes [beginCache()|CBaseController::beginCache] et [endCache()|CBaseController::endCache] directement dans le layout. Cela est -du au fait que le layout est appliqué dans la méthode [CController::render()] +dû au fait que le layout est appliqué dans la méthode [CController::render()] une fois que la vue a été évaluée. Pour cacher l'intégralité d'une page, il faut éviter l'exécution de l'action qui génère le contenu de la page. Pour ce faire, -nous pouvons utiliser [COutputCache] en tant que +nous pouvons utiliser [COutputCache] en tant que [filtre](/doc/guide/basics.controller#filter). Le code suivant montre comment configurer le filtre de cache : @@ -36,16 +35,16 @@ public function filters() } ~~~ -La configuration du filtre ci-dessus sera appliqué à toutes les actions +La configuration du filtre ci-dessus sera appliquée à toutes les actions du contrôleur. Nous pouvons limiter la portée du cache en utilisant l'opérateur "+". Plus de détails peuvent être trouvés dans la section [filtre](/doc/guide/basics.controller#filter). -> Tip: Il est possible d'utiliser [COutputCache] en tant que filtre +> Astuce: Il est possible d'utiliser [COutputCache] en tant que filtre car il dérive de [CFilterWidget], ce qui signifie que c'est à la fois -un filtre et un widget. En réalité un widget et un filtre sont très proches : +un filtre et un widget. En réalité un widget et un filtre sont très proches: un widget (filtre) commence avant qu'un contenu (action) soit évalué et le widget (filtre) se termine une fois que le contenu entre les balises (action) est évalué. -
$Id: caching.page.txt 1014 2009-05-10 12:25:55Z qiang.xue $
\ No newline at end of file +
$Id: caching.page.txt 1016 2013-11-22 $
diff --git a/docs/guide/fr/database.ar.txt b/docs/guide/fr/database.ar.txt index 2acc3c13ea..a3684edff1 100644 --- a/docs/guide/fr/database.ar.txt +++ b/docs/guide/fr/database.ar.txt @@ -44,12 +44,12 @@ des requêtes n'impliquant pas de code SQL complexe. Yii DAO est préférable da cas de requêtes SQL complexes. -Etablir une connection à la BD ------------------------------- +Etablir une connexion à la base de données +------------------------------------------ -AR à besoin d'une connection à une BD pour effectuer les tâches relatives à la base. +AR à besoin d'une connexion à une BD pour effectuer les tâches relatives à la base. Par défaut, l'application component `db` sera utilisé pour récupérer l'instance -[CDbConnection] qui servira pour la connection à la base. Voici un exemple de configuration: +[CDbConnection] qui servira pour la connexion à la base. Voici un exemple de configuration: ~~~ [php] @@ -66,13 +66,13 @@ return array( ~~~ > Tip: Comme Active Record dépend des metadata décrivant les tables pour -connaitre les informations d'une colonne, cela prend du temps lors -de la lecture des metadata et de leurs analyse. Si il est peu probable que +connaître les informations d'une colonne, cela prend du temps lors +de la lecture des metadata et de leur analyse. S'il est peu probable que le schéma de votre base change, il est conseillé d'activer le cache sur le -schéma en positionnant la propriété [CDbConnection::schemaCachingDuration] sur +schéma en fixant la propriété [CDbConnection::schemaCachingDuration] à une valeur supérieure à 0. -Le support pour AR est limité par le SGBD. Actuellement, seulement les SGBD suivant +Le support pour AR est limité par le SGBD. Actuellement, seulement les SGBD suivants le supportent: - [MySQL 4.1 ou plus](http://www.mysql.com) @@ -98,7 +98,7 @@ Définition d'une classe AR Pour accéder à une table en base, il faut tout d'abord définir une classe AR en héritant de [CActiveRecord]. Chaque classe AR représente une seule table, et une instance AR représente -une ligne dans cette table. L'exemple suivant montre le code minimum requit pour la classe AR représentant +une ligne dans cette table. L'exemple suivant montre le code minimum requis pour la classe AR représentant la table `tbl_post`. ~~~ @@ -120,7 +120,7 @@ class Post extends CActiveRecord > Tip: Comme les classes AR sont souvent référencées à de nombreux endroits, il est > possible d'importer tout le répertoire contenant les classes AR, plutôt que de les > inclure une par une. Par exemple, si tous vos fichiers de classes AR sont dans -> `protected/models`, il est possible de configurer l'application comme suivant: +> `protected/models`, il est possible de configurer l'application comme suit: > ~~~ > [php] > return array( @@ -132,12 +132,12 @@ class Post extends CActiveRecord Par défaut, le nom de la classe AR est le même que celui de la table en base. Il est possible de surcharger la méthode [tableName()|CActiveRecord::tableName] si -ceux ci sont différents. La méthode [model()|CActiveRecord::model] est déclarée dans +ceux-ci sont différents. La méthode [model()|CActiveRecord::model] est déclarée dans chaque classe AR (explication peu après). > Info: Pour utiliser les [préfixes de table](/doc/guide/database.dao#using-table-prefix) -> introduit dans la version 1.1.0, la méthode [tableName()|CActiveRecord::tableName] -> pour une classe AR peut etre surchargée comme suivant, +> introduits dans la version 1.1.0, la méthode [tableName()|CActiveRecord::tableName] +> pour une classe AR peut être surchargée comme suit, > ~~~ > [php] > public function tableName() @@ -159,7 +159,7 @@ $post->title='un post de test'; ~~~ Bien que la propriété `title` ne soit jamais déclarée explicitement dans la classe `Post`, -il est tout de même possible d'y accéder dans le code précédant. Cela s'explique car `title` +il est tout de même possible d'y accéder dans le code précédent. Cela s'explique car `title` est une colonne dans la table `tbl_post`, et que CActiveRecord la rend accessible en tant que propriété grâce aux méthodes PHP `__get()`. Une exception sera levée si un accès est tenté sur une colonne inexistante. @@ -172,7 +172,7 @@ casses. L'utilisation des minuscules permet d'éliminer ce problème. AR dépend de la définition correcte des clés primaires sur les tables. Si une table n'a pas de clé primaire, il est nécessaire que la classe AR correspondante spécifie quelle(s) colonne(s) -doivent être la clé primaire en surchargeant la méthode `primaryKey()`, +doit(vent) être la clé primaire en surchargeant la méthode `primaryKey()`, ~~~ [php] @@ -202,11 +202,11 @@ $post->save(); ~~~ Si la clé primaire de la table est en auto incrément, après l'insertion, -l'instance AR contiendra la clé primaire mise à jour. Dans l'exemple ci dessus, +l'instance AR contiendra la clé primaire mise à jour. Dans l'exemple ci-dessus, la propriété `id` sera égale à la valeur de la clé primaire du nouveau post inséré, même si elle n'est jamais changée implicitement. -Si une colonne a été défini avec une valeur statique par défaut dans le schéma de la table, +Si une colonne a été définie avec une valeur statique par défaut dans le schéma de la table, la propriété correspondante dans l'instance AR aura automatiquement cette valeur suite à la création de cette instance. Une façon de changer cette valeur par défaut est de déclarer explicitement la propriété dans la classe AR: @@ -234,7 +234,7 @@ est possible d'utiliser le code suivant: $post=new Post; $post->create_time=new CDbExpression('NOW()'); // $post->create_time='NOW()'; ne fonctionnera pas car -// 'NOW()' sera traité comme une chaine de caractères +// 'NOW()' sera traité comme une chaîne de caractères $post->save(); ~~~ @@ -245,17 +245,17 @@ de Yii. Par exemple, en activant [CWebLogRoute] dans la configuration de l'appli les requêtes SQL seront affichées à la fin de chaque page web. Depuis la version 1.0.5, il est possible de mettre [CDbConnection::enableParamLogging] à true dans la configuration de l'application pour que les valeurs en paramètres liées aux requêtes SQL -apparaissent aussi dans le log. +apparaissent aussi dans le journal des logs. Lecture des enregistrements --------------------------- -Pour lire des données d'une table, on appel une des méthodes `find` comme suivant. +Pour lire des données d'une table, on appelle une des méthodes `find` comme suit: ~~~ [php] -// trouve la première ligne satisfaisant les conditions +// trouve la première ligne satisfaisant la condition $post=Post::model()->find($condition,$params); // trouve la ligne avec la clé primaire spécifiée $post=Post::model()->findByPk($postID,$condition,$params); @@ -265,7 +265,7 @@ $post=Post::model()->findByAttributes($attributes,$condition,$params); $post=Post::model()->findBySql($sql,$params); ~~~ -Ci dessus, l'appel de la méthode `find` est effectué au travers de `Post::model()`. +Ci-dessus, l'appel de la méthode `find` est effectué au travers de `Post::model()`. Ne pas oublier que la méthode statique `model()` est indispensable pour toutes les classes AR. La méthode retourne une instance AR qui est utilisée pour accéder aux méthodes de la classe (de manière similaire aux méthodes des classes statiques) dans un contexte object. @@ -275,7 +275,7 @@ retournera une instance de `Post` dont les propriétés contiendront les valeurs correspondantes de la ligne de la table. Il est alors possible de lire les valeurs chargées de la même manière que pour un objet classique, par exemple, `echo $post->title;`. -La méthode `find` retournera null si rien n'a été trouvé dans la base. +La méthode `find` retournera null si rien n'a été trouvé en base. Lors de l'appel à `find`, on utilise `$condition` et `$params` pour spécifier les conditions à la requête. Ici `$condition` est une string représentant la clause `WHERE` d'une requête SQL, @@ -288,32 +288,32 @@ et `$params` est un tableau de paramètres dont les valeurs devront être liées $post=Post::model()->find('postID=:postID', array(':postID'=>10)); ~~~ -> Note: Dans le code ci dessus, il est possible que vous deviez échapper la référence +> Note: Dans le code ci-dessus, il est possible que vous deviez échapper la référence à la colonne `postID` pour certain SGBD. Par exemple, si vous utilisez PostgreSQL, vous devez écrire la condition `"postID"=:postID`, car PostgreSQL traite par défaut les colonnes comme insensible à la casse. -Il est aussi possible d'utiliser `$condition` pour spécifier des conditions plus complèxes. -A la place d'une chaine de caractères, `$condition` pourra être une instance de [CDbCriteria], +Il est aussi possible d'utiliser `$condition` pour spécifier des conditions plus complexes. +A la place d'une chaîne de caractères, `$condition` pourra être une instance de [CDbCriteria], ce qui permettra de spécifier des conditions autres que la clause `WHERE`. Par exemple, ~~~ [php] $criteria=new CDbCriteria; -$criteria->select='titre'; // selectionne seulement la colonne 'title' +$criteria->select='titre'; // sélectionne seulement la colonne 'title' $criteria->condition='postID=:postID'; $criteria->params=array(':postID'=>10); $post=Post::model()->find($criteria); // $params n'est pas nécessaire ~~~ Notez que quand [CDbCriteria] est utilisé dans une condition de requête, le -paramètre `$params` n'est plus requit puisqu'il peut être spécifié dans [CDbCriteria], -comme on peut le voir ci dessus. +paramètre `$params` n'est plus requis puisqu'il peut être spécifié dans [CDbCriteria], +comme on peut le voir ci-dessus. Une autre méthode que l'utilisation de [CDbCriteria] est le passage d'un tableau à la méthode `find`. Les clés et valeurs du tableau correspondent alors respectivement aux noms et valeurs -des critères. Ainsi l'exemple ci dessus peut être réécrit comme suivant, +des critères. Ainsi l'exemple ci-dessus peut être réécrit comme suit: ~~~ [php] @@ -324,16 +324,15 @@ $post=Post::model()->find(array( )); ~~~ -> Info: Quand une condition à pour but de faire correspondre des colonnes avec +> Info: Quand une condition a pour but de faire correspondre des colonnes avec des valeurs, il est possible d'utiliser [findByAttributes()|CActiveRecord::findByAttributes]. Le paramètre `$attributes` est alors un tableau de valeurs avec pour clés les noms des colonnes. Dans certains frameworks, cette tâche est accomplie en appelant des méthodes telles que `findByNameAndTitle`. -Bien que cette approche semble intéressante, elle sème souvent la confusion, et fait apparaitre +Bien que cette approche semble intéressante, elle sème souvent la confusion, et fait apparaître des conflits et problèmes comme la sensibilité à la casse des noms de colonnes. -Quand plusieurs lignes de données correspondent à la condition de la requête, il est -possible de les ramener d'un coup en utilisant les méthodes `findAll`, chacune d'entres elles -possédant sa méthode équivalente `find`, décrites précédemment. +Quand plusieurs lignes de données correspondent à la condition spécifiée dans la requête, il est +possible de toutes les récupérer en une seule opération en utilisant les méthodes `findAll` ci-dessous, chacune d'entres elles possédant sa méthode équivalente `find`, telles que décrites précédemment. ~~~ [php] @@ -347,12 +346,12 @@ $posts=Post::model()->findAllByAttributes($attributes,$condition,$params); $posts=Post::model()->findAllBySql($sql,$params); ~~~ -Si aucun enregistrement ne correspond à la condition de la requête, `findAll` +Si aucun enregistrement ne correspond à la condition de la requête, `findAll` retournera un tableau vide. Ce comportement est différent en cela de `find` qui -retournera null is aucun enregistrement ne correspond. +retournera null si aucun enregistrement ne correspond. -En plus des méthodes `find` et `findAll` décrites ci dessus, les méthodes suivantes -sont aussi incluses: +En plus des méthodes `find` et `findAll` décrites ci-dessus, les méthodes suivantes +sont aussi disponibles: ~~~ [php] @@ -360,7 +359,7 @@ sont aussi incluses: $n=Post::model()->count($condition,$params); // retourne le nombre d'enregistrements de la requête SQL spécifiée $n=Post::model()->countBySql($sql,$params); -// regarde si il y a au moins un enregistrement satisfaisant la condition spécifiée +// regarde s'il y a au moins un enregistrement satisfaisant la condition spécifiée $exists=Post::model()->exists($condition,$params); ~~~ @@ -379,9 +378,9 @@ $post->save(); // enregistrement dans la base Comme l'on peut le voir, la même méthode [save()|CActiveRecord::save] est utilisée pour les opérations d'insertion et de mise à jour. Si une instance AR est créée -en utilisant l'opérateur `new`, l'appel à [save()|CActiveRecord::save] insèrera une +en utilisant l'opérateur `new`, l'appel à [save()|CActiveRecord::save] insère une nouvelle ligne dans la table; si l'instance AR provient du résultat de méthodes telles que -`find` ou `findAll`, l'appel à [save()|CActiveRecord::save] mettrais à jour la ligne +`find` ou `findAll`, l'appel à [save()|CActiveRecord::save] met à jour la ligne existante dans la table. En fait, il est possible d'utiliser [CActiveRecord::isNewRecord] pour savoir si une instance AR existe ou non. @@ -399,7 +398,7 @@ Post::model()->updateByPk($pk,$attributes,$condition,$params); Post::model()->updateCounters($counters,$condition,$params); ~~~ -Ci dessus, `$attributes` est un tableau de valeurs de colonnes dont les clés correspondent +Ci-dessus, `$attributes` est un tableau de valeurs de colonnes dont les clés correspondent aux noms des colonnes; `$counters` est une tableau de valeurs incrémentales dont les clés sont les noms des colonnes; et `$condition` et `$params` sont les mêmes que dans les sections précédentes. @@ -416,7 +415,7 @@ $post->delete(); // supprime la ligne dans la table ~~~ Attention, après suppression, l'instance AR reste inchangée, même si la ligne correspondante -dans la table à déjà été effectivement supprimée. +dans la table a déjà été effectivement supprimée. Les méthodes suivantes de la classe sont incluses afin de pouvoir supprimer des lignes sans les avoir chargées auparavant: @@ -435,13 +434,13 @@ Validation des données Durant une insertion ou mise à jour d'une ligne, il est souvent nécessaire de vérifier si les valeurs des colonnes suivent bien certaines règles. C'est particulièrement important si ces valeurs proviennent des utilisateurs finaux. -En général, il ne faut jamais faire confiance aux données provenant du coté client. +En général, il ne faut jamais faire confiance aux données provenant du côté client. AR va effectuer cette validation de données automatiquement quand la méthode [save()|CActiveRecord::save] est appelée. La validation se base sur les règles définies dans la méthodes [rules()|CModel::rules] de la classe AR. Pour plus de détails sur comment écrire ces règles de validation, veuillez consulter la section [Declaration des règles de validation](/doc/guide/form.model#declaring-validation-rules). -Voici ci dessous le déroulement typique lors de l'enregistrement d'une ligne: +Voici ci-dessous le déroulement typique lors de l'enregistrement d'une ligne: ~~~ [php] @@ -455,7 +454,7 @@ else } ~~~ -Quand les données à insérer ou mettre à jour sont fournis par les utilisateurs finaux +Quand les données à insérer ou mettre à jour sont fournies par les utilisateurs finaux sous forme HTML, il faut les assigner aux propriétés correspondantes de l'AR. Voici comment il est possible de faire cela: @@ -466,9 +465,9 @@ $post->content=$_POST['content']; $post->save(); ~~~ -Si il y avait de nombreuses colonnes, il faudrait faire de nombreuses assignations. +S'il y avait de nombreuses colonnes, il faudrait faire de nombreuses assignations. Cela peut être évité en utilisant la propriété [attributes|CActiveRecord::attributes] -comme montré ci dessous. Plus de détails sont disponibles dans les sections +comme montré ci-dessous. Plus de détails sont disponibles dans les sections [Securing Attribute Assignments](/doc/guide/form.model#securing-attribute-assignments) et [Creating Action](/doc/guide/form.action). @@ -554,7 +553,7 @@ Named Scopes > Note: Le support pour les named scopes est disponible depuis la version 1.0.5. > L'idée originelle des named scopes provient de Ruby on Rails. -Un *named scope* représente un critère *named* d'une requête qui peut etre combiné avec +Un *named scope* représente un critère *named* d'une requête qui peut être combiné avec d'autres named scopes et appliqués ensemble dans une requête sur un active record. Les Named scopes sont normalement déclarés dans la méthode [CActiveRecord::scopes()] comme des paires de noms-critères. Le code suivant déclare deux named scopes, `published` et `recently`, dans la classe modélisant `Post`: @@ -579,7 +578,7 @@ class Post extends CActiveRecord } ~~~ -Chaque named scope est déclaré comme un tableau qui est utilisé pour initialiser une instance [CDbCriteria]. Par example, le named scope `recently` indique que la propriété `order` sera `create_time DESC` et que la propriété `limit` sera 5, ce qui donne un critère de requête qui retournera les 5 posts les plus récents. +Chaque named scope est déclaré comme un tableau qui est utilisé pour initialiser une instance [CDbCriteria]. Par exemple, le named scope `recently` indique que la propriété `order` sera `create_time DESC` et que la propriété `limit` sera 5, ce qui donne un critère de requête qui retournera les 5 posts les plus récents. Les Named scopes vont principalement être utilisés pour altérer les appels aux méthodes `find`. Plusieurs named scopes peuvent être chainés ensemble pour restreindre les résultats de la requête. Par exemple, pour trouver les posts récemment publiés, il est possible de faire: @@ -616,19 +615,19 @@ public function recently($limit=5) } ~~~ -Il est alors possible d'utiliser le code ci dessous pour récupérer les 3 posts récemment publiés: +Il est alors possible d'utiliser le code ci-dessous pour récupérer les 3 posts récemment publiés: ~~~ [php] $posts=Post::model()->published()->recently(3)->findAll(); ~~~ -Si le paramètre 3 ci dessus n'est pas spécifié, les 5 derniers posts publiés serait renvoyés par défaut. +Si le paramètre 3 ci-dessus n'est pas spécifié, les 5 derniers posts publiés sont renvoyés par défaut. -### Named Scope par défaut +### Scope par défaut -Une classe peut avoir un named scope par défaut qui serait appliqués à toutes les requêtes (dont les relationelles) sur un modèle. Par exemple, un site web supportant plusieurs langues souhaiterait peut etre ne vouloir afficher que le contenu correspondant au langage que l'utilisateur à défini. Comme il se peut qu'il y ait beaucoup de requêtes concernant le contenu du site, il est possible de définir un named scope par défaut pour régler ce problème. Pour cela, il faut surcharger la méthode [CActiveRecord::defaultScope] comme ci dessous, +Une classe peut avoir un named scope par défaut qui peut être appliqué à toutes les requêtes (dont les relationelles) sur un modèle. Par exemple, un site web supportant plusieurs langues souhaite afficher uniquement le contenu correspondant à la langue définie par l'utilisateur. Comme il se peut qu'il y ait beaucoup de requêtes concernant le contenu du site, il est possible de définir un named scope par défaut pour régler ce problème. Pour cela, il faut surcharger la méthode [CActiveRecord::defaultScope] comme ci-dessous: ~~~ [php] @@ -643,13 +642,13 @@ class Content extends CActiveRecord } ~~~ -Tous les appels aux méthodes find utiliseront automatiquement le critère défini ci dessus: +Tous les appels aux méthodes find utiliseront alors automatiquement le critère défini ci-dessus. ~~~ [php] $contents=Content::model()->findAll(); ~~~ -Attention car les named scope par défaut ne s'applique que sur les requêtes `SELECT`. Ils sont ignorés pour les requêtes `INSERT`, `UPDATE` et `DELETE`. +> Note: Le scope par défaut et les scopes nommés ne s'appliquent que sur les requêtes `SELECT`. Ils sont ignorés pour les requêtes `INSERT`, `UPDATE` et `DELETE`. -
$Id: database.ar.txt 1681 2010-01-08 03:04:35Z qiang.xue $
+
$Id: database.ar.txt 1682 2013-11-20$
diff --git a/docs/guide/fr/database.dao.txt b/docs/guide/fr/database.dao.txt index 1e44ca76ba..b3413b4f52 100644 --- a/docs/guide/fr/database.dao.txt +++ b/docs/guide/fr/database.dao.txt @@ -2,7 +2,7 @@ Data Access Objects (DAO) ========================= Data Access Objects (DAO) permet l'accès aux données stockées dans divers SGBD -au travers d'une API générique. Ainsi, le SGBD sous jacent peut etre changé sans +au travers d'une API générique. Ainsi, le SGBD sous jacent peut être changé sans avoir à modifier le code utilisant DAO pour accéder aux données. Yii DAO est basé sur les [PHP Data Objects @@ -13,37 +13,37 @@ PDO et du pilote PDO spécifique pour le SGBD utilisé (par exemple `PDO_MYSQL`) Yii DAO est constitué des quatres classes suivantes: - - [CDbConnection]: représente une connection à une base. + - [CDbConnection]: représente une connexion à une base. - [CDbCommand]: représente une requête SQL. - - [CDbDataReader]: représente un flux de donnée résultant d'une requête. + - [CDbDataReader]: représente un flux de données résultant d'une requête. - [CDbTransaction]: représente une transaction. L'utilisation de Yii DAO dans différents cas de figure va maintenant être présenté. -Etablir une connection à une base +Etablir une connexion à une base --------------------------------- -Pour établir une connection à une base, il est nécessaire de créer une instance de [CDbConnection] +Pour établir une connexion à une base, il est nécessaire de créer une instance de [CDbConnection] et de l'activer. Pour cela un DSN (Data Source Name) contenant les informations -de connection devra être spécifié. Un nom d'utilisateur et un mot de passe, optionnels, -peuvent aussi être renseigné ici. Une exception sera levée si une erreur se produit durant -la connection (par exemple en cas de mauvais DSN ou un utilisateur/mot de passe invalide). +de connexion devra être spécifié. Un nom d'utilisateur et un mot de passe, optionnels, +peuvent aussi être renseignés ici. Une exception sera levée si une erreur se produit durant +la connexion (par exemple en cas de mauvais DSN ou un utilisateur/mot de passe invalide). ~~~ [php] $connection=new CDbConnection($dsn,$username,$password); -// établit une connection. try...catch pour récupérer les éventuelles exceptions +// établit une connexion. try...catch pour récupérer les éventuelles exceptions $connection->active=true; ...... -$connection->active=false; // fermeture de la connection +$connection->active=false; // fermeture de la connexion ~~~ Le format du DSN varie selon le pilote PDO choisi. De manière générale, un DSN -est constitué du nom du pilote PDO, suivi d'un point virgule, suivi d'une chaine -dont la syntaxe dépend du pilote. Voir la [documentation +est constitué du nom du pilote PDO, suivi d'un point virgule, suivi d'une chaîne +de caractères dont la syntaxe dépend du pilote. Voir la [documentation PDO](http://www.php.net/manual/en/pdo.construct.php) pour plus d'information. -Ci dessous une liste avec les formats courammant utilisés: +Ci-dessous une liste avec les formats couramment utilisés: - SQLite: `sqlite:/path/to/dbfile` - MySQL: `mysql:host=localhost;dbname=testdb` @@ -56,7 +56,7 @@ de l'utiliser comme un [application component](/doc/guide/basics.application#application-component). Pour cela, il faut configurer l'application component `db` (ou un autre nom) dans l'[application configuration](/doc/guide/basics.application#application-configuration) selon le modèle -suivant, +suivant: ~~~ [php] @@ -69,42 +69,42 @@ array( 'connectionString'=>'mysql:host=localhost;dbname=testdb', 'username'=>'root', 'password'=>'password', - 'emulatePrepare'=>true, // requit pour certaines installations MySQL + 'emulatePrepare'=>true, // requis pour certaines installations MySQL ), ), ) ~~~ -Il est dès lors possible d'accéder à la connection au travers de `Yii::app()->db` +Il est dès lors possible d'accéder à la connexion au travers de `Yii::app()->db` qui est activé automatiquement, à moins que [CDbConnection::autoConnect] ne soit explicitement -mis à false. Grâce à cette méthode, cette connection unique sera accessible où que l'on soit +fixé à false. Grâce à cette méthode, cette connexion unique sera accessible partout où que l'on soit dans le code. Exécution de requêtes SQL ------------------------- -Une fois qu'une connection à été établit, les requêtes SQL peuvent être exécutées +Une fois qu'une connexion à été établie, les requêtes SQL peuvent être exécutées grâce à la classe [CDbCommand]. Il faut pour cela créer une instance [CDbCommand] en appelant [CDbConnection::createCommand()] et en spécifiant la requête SQL. ~~~ [php] -$connection=Yii::app()->db; // vous devez avoir une connection "db" -// Sinon il est possible de créer cette connection explicitement: +$connection=Yii::app()->db; // vous devez avoir une connexion "db" +// Sinon il est possible de créer cette connexion explicitement: // $connection=new CDbConnection($dsn,$username,$password); $command=$connection->createCommand($sql); -// Si besoin, la requête peut etre mise à jour comme suivant: +// Si besoin, la requête peut être mise à jour comme suit: // $command->text=$newSQL; ~~~ Une requete SQL est exécutée au travers de [CDbCommand] selon l'une des 2 méthodes suivantes: - [execute()|CDbCommand::execute]: Exécute une requête SQL sans retour de données, -tels que `INSERT`, `UPDATE` ou `DELETE`. Si la requête se déroule sans erreur, -cette fonction retournera le nombre de ligne affectées. +telles que les requêtes `INSERT`, `UPDATE` ou `DELETE`. Si la requête se déroule sans erreur, +cette fonction retournera le nombre de lignes affectées. - [query()|CDbCommand::query]: Exécute une requête SQL retournant des données -tels que `SELECT`. Si la requête se déroule sans erreur, cette fonction retournera une +telle que `SELECT`. Si la requête se déroule sans erreur, cette fonction retournera une instance de [CDbDataReader] dans laquelle on pourra parcourir les lignes. Dans un soucis de simplicité, plusieurs méthodes `queryXXX()` ont aussi été implémentées retournant directement les résultats. @@ -112,7 +112,7 @@ Une exception sera levée si une erreur se produit durant l'exécution de la req ~~~ [php] -$rowCount=$command->execute(); // exécute une requête SQL sans reour de données +$rowCount=$command->execute(); // exécute une requête SQL sans retour de données $dataReader=$command->query(); // exécute une requête SQL $rows=$command->queryAll(); // exécute et retourne toutes les lignes $row=$command->queryRow(); // exécute et retourne la première ligne @@ -126,7 +126,7 @@ Parcourir les résultats Une fois que [CDbCommand::query()] à généré l'instance [CDbDataReader], il est possible de récupérer les lignes en appelant successivement la méthode [CDbDataReader::read()]. Il est aussi possible d'utiliser [CDbDataReader] -directement dans une boucle `foreach` pour récupérer les données lignes par lignes. +directement dans une boucle `foreach` pour récupérer les données ligne par ligne. ~~~ [php] @@ -140,23 +140,23 @@ $rows=$dataReader->readAll(); ~~~ > Note: Contrairement à [query()|CDbCommand::query], les méthodes `queryXXX()` -renvoient les données directement. Par exemple, [queryRow()|CDbCommand::queryRow] -retourne un tableau representant la première ligne du résultat. +renvoient directement les données. Par exemple, [queryRow()|CDbCommand::queryRow] +retourne un tableau représentant la première ligne du résultat. Utilisation des transactions ---------------------------- -Lorsqu'une application exécute plusieurs requêtes de lecture et/ou écriture, -il est important d'etre certain que la base ne sera pas laissée dans un état ou +Lorsqu'une application exécute plusieurs requêtes de lecture et/ou d'écriture, +il est important d'être certain que la base ne soit pas laissée dans un état où toutes les requêtes n'ont pas été achevées. Une transaction, représentée comme une instance de [CDbTransaction] dans Yii, pourra être utilisée selon le schéma suivant: - Début de la transaction. - Exécution des requêtes une à une. Aucune modification à la base ne sera visible en dehors du contexte. - - Commit la transaction. Toutes les modifications sont alors visibles si la transaction se déroule sans erreur. + - Validation (Commit) de l'ensemble de la transaction. Toutes les modifications sont alors visibles si la transaction se déroule sans erreur. - Si une seule requête échoue, la transaction entière est annulée (roll back). -Le déroulement ci dessus peut etre implémenté comme suivant: +Le déroulement ci-dessus peut être implémenté comme suit: ~~~ [php] @@ -180,12 +180,12 @@ Paramètres liés Pour faire face aux [attaques par injection SQL](http://en.wikipedia.org/wiki/SQL_injection) et pour améliorer les performances lors des exécutions répétées de requêtes SQL, il est possible de "préparer" les requêtes SQL, avec optionnellement des marques pour les paramètres, qui seront -remplacées par les vrai paramètres durant l'étape de "liage" (binding) des paramètres. +remplacées par les valeurs des vrais paramètres durant l'étape de "liaison" (binding) des paramètres. -Les marques pour ces paramètres peuvent être nommés (représenté comme des "tokens") ou non-nommés -(représenté comme des points d'interrogation). Un appel à [CDbCommand::bindParam()] ou [CDbCommand::bindValue()] -permettra de remplacer ces marques par les vrai valeurs. Ces paramètres n'ont pas besoin d'être entre -parenthèses: Le pilote de la base le fera pour vous. Le liage des paramètre devra être fait avant +Les marques pour ces paramètres peuvent être nommées (représentées comme des "tokens") ou non-nommées +(représentées comme des points d'interrogation). Un appel à [CDbCommand::bindParam()] ou [CDbCommand::bindValue()] +permettra de remplacer ces marques par les vraies valeurs. Ces paramètres n'ont pas besoin d'être entre +parenthèses: Le pilote de la base le fera pour vous. La liaison des paramètres devra évidemment être faite avant l'exécution de la requête. ~~~ @@ -193,9 +193,9 @@ l'exécution de la requête. // requête SQL avec 2 marques ":username" et ":email" $sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)"; $command=$connection->createCommand($sql); -// remplace la marque ":username" avec la vrai valeur de username +// remplace la marque ":username" par la vraie valeur de username $command->bindParam(":username",$username,PDO::PARAM_STR); -// remplace la marque ":email" avec la vrai valeur email +// remplace la marque ":email" par la vraie valeur email $command->bindParam(":email",$email,PDO::PARAM_STR); $command->execute(); // insertion d'une ligne avec les nouveaux paramètres @@ -208,9 +208,9 @@ Les méthodes [bindParam()|CDbCommand::bindParam] et [bindValue()|CDbCommand::bindValue] sont très similaires. La seule différence est que le premier lie un paramètre avec une référence de variable PHP alors que le deuxième le lie avec la valeur de cette variable. Pour les paramètres représentant -de gros blocs mémoire, le premier est préféré pour des raisons de performance. +de gros blocs mémoire, la premiere méthode est préférable pour des raisons de performances. -Pour plus de détails sur les paramètres liant, voir la [ +Pour plus de détails sur la liaison de paramètres, voir la [ documentation PHP en rapport](http://www.php.net/manual/en/pdostatement.bindparam.php). Colonnes liées @@ -230,7 +230,7 @@ $dataReader->bindColumn(1,$username); $dataReader->bindColumn(2,$email); while($dataReader->read()!==false) { - // $username et $email contiennent les username et email de la ligne courante + // $username et $email contiennent les username et email de la ligne courante } ~~~ @@ -238,17 +238,17 @@ Utilisation des préfixes de table --------------------------------- A partir de la version 1.1.0, Yii supporte les préfixes de tables. -Un préfixe de table est une chaine de caractère préfixant le nom d'une table. -Cette technique est très utilisée dans des environnements d'hébergements partagés -dans lesquels des applications partagent une meme base de donnée et ainsi utilisent +Un préfixe de table est une chaîne de caractères préfixant le nom d'une table. +Cette technique est très utilisée dans des environnements d'hébergements mutualisés +dans lesquels des applications partagent une même base de donnée et ainsi utilisent différents préfixes pour se différencier l'une de l'autre. Par exemple, une application -pourrait utiliser un préfix `tbl_` et une autre `yii_`. +pourrait utiliser un préfixe `tbl_` et une autre `yii_`. Pour utiliser les préfixes de table, il faut configurer la propriété [CDbConnection::tablePrefix] -comme la valeur du préfixe souhaité. Puis, dans les requêtes SQL, il est possible d'utiliser +avec la valeur du préfixe souhaité. Puis, dans les requêtes SQL, il est possible d'utiliser `{{TableName}}` qui correspond au nom de la table sans le préfixe. Par exemple, si la base contient -une table `tbl_user` dans laquelle `tbl_` est configuré pour être le préfixe, il est possible d'utiliser -le code suivant pour connaitre les utilisateurs: +une table `tbl_user` pour laquelle `tbl_` est défini comme le préfixe des tables, il est possible +alors d'utiliser le code suivant pour accéder à la table des utilisateurs: ~~~ [php] @@ -256,4 +256,4 @@ $sql='SELECT * FROM {{user}}'; $users=$connection->createCommand($sql)->queryAll(); ~~~ -
$Id: database.dao.txt 2266 2010-07-17 13:58:30Z qiang.xue $
+
$Id: database.dao.txt 2268 2013-11-20 $
diff --git a/docs/guide/fr/database.overview.txt b/docs/guide/fr/database.overview.txt index 4804107ebe..5dae836e97 100644 --- a/docs/guide/fr/database.overview.txt +++ b/docs/guide/fr/database.overview.txt @@ -3,6 +3,6 @@ Travailler avec les bases de données La gestion des bases de données dans Yii est très complète et performante. Basé sur l'extension PHP Data Objects (PDO), Yii Data Access Objects (DAO) permet d'accéder à différents systèmes de gestion de base de données (SGBD) au travers d'une seule interface uniforme. Les applications développées avec Yii DAO sont utilisables immédiatement sous d'autres SGBD, sans modification des sources. Yii Active Record (AR), implémenté selon l'approche bien connue Object-Relational Mapping (ORM), simplifie grandement les accès à la base de donnée. En représentant les tables par des classes et les lignes par des instances, Yii AR élimine la tache répétitive d'écriture des requêtes SQL pour les opérations CRUD (create, read, update et delete). -Bien que Yii DAO et AR soient capable de manipuler la quasi totalité des tâches relatives à la base de donnée, vous avez toujours la possibilité d'utiliser votre propre librairie de base de données dans votre application Yii. En effet, le framework Yii est conçu pour etre utilisé en parallèle avec d'autres librairies tierces. +Bien que Yii DAO et AR soient capable de manipuler la quasi totalité des tâches relatives à la base de donnée, vous avez toujours la possibilité d'utiliser votre propre librairie de base de données dans votre application Yii. En effet, le framework Yii est conçu pour être utilisé en parallèle avec d'autres librairies tierces.
$Id: database.overview.txt 163 2008-11-05 12:51:48Z weizhuo $
diff --git a/docs/guide/fr/form.action.txt b/docs/guide/fr/form.action.txt index ae35276e1e..43ceaa3e9f 100644 --- a/docs/guide/fr/form.action.txt +++ b/docs/guide/fr/form.action.txt @@ -2,9 +2,9 @@ Créer l'action ============== Une fois qu'on a créé le modèle, on peut commencer à écrire la logique -nécessaire à la manipulation du modèle. Cette logique est placée au sein -d'une action d'un contrôleur. Dans l'exemple du formulaire de connection, -on utilise le code suivant: +nécessaire à la manipulation de ce modèle. Cette logique est placée au sein +d'une action d'un contrôleur. Dans l'exemple du formulaire de connexion, +on peut typiquement utiliser le code suivant: ~~~ [php] @@ -13,31 +13,32 @@ public function actionLogin() $model=new LoginForm; if(isset($_POST['LoginForm'])) { - // récupère les données de l'utilisateur + // récupère les données postées par l'utilisateur $model->attributes=$_POST['LoginForm']; - // valide les données soumises par l'utilisateur et redirige - // la page précédente si les données sont validées. + // valide les données envoyées par l'utilisateur et + // redirige sur la page précédente si les données sont validées. if($model->validate()) $this->redirect(Yii::app()->user->returnUrl); } - // affiche le formulaire de connection + // affiche le formulaire de connexion $this->render('login',array('model'=>$model)); } ~~~ -Ci dessus, on crée tout d'abord une instance du modèle `LoginForm`; si c'est une requète -POST (signifiant que le formulaire de connection à été soumis par l'utilisateur), on -fourni au `$model` les données soumises `$_POST['LoginForm']`; puis l'on valide ces données -et si la validation réussie, on redirige le navigateur de l'utilisateur vers la page +Ci-dessus, on crée tout d'abord une instance du modèle `LoginForm`; si c'est une requète +POST (signifiant que le formulaire de connexion à été soumis par l'utilisateur), on +fournit au `$model` les données soumises `$_POST['LoginForm']`; puis l'on valide ces données +et si la validation réussi, on redirige le navigateur de l'utilisateur vers la page qui nécessitait l'authentification. Si la validation échoue, ou si l'action est accédée -pour la premier fois, on affiche la vue `login` avec le contenu qui va être décrit dans +pour la premiere fois, on affiche la vue `login` avec le contenu qui va être décrit dans la section qui suit. > Tip: Dans l'action `login`, on utilise `Yii::app()->user->returnUrl` pour récupérer l'URL de la page qui nécessitait l'authentification. Le component `Yii::app()->user` est -de type [CWebUser] (ou une de ses classes héritées) représentant l'information sur la session utilisateur (par exemple username, status). Pour plus de détails, voir [Authentication and Authorization](/doc/guide/topics.auth). +de type [CWebUser] (ou une de ses classes héritées) et contient les informations sur la +session utilisateur (par exemple username, status). Pour plus de détails, voir [Authentification et autorisation](/doc/guide/topics.auth). -Regardons plus attentivement l'opération PHP suivante qui apparait dans l'action `login`: +Regardons plus attentivement l'opération PHP suivante qui apparaît dans l'action `login`: ~~~ [php] @@ -46,9 +47,9 @@ $model->attributes=$_POST['LoginForm']; Comme décrit dans [Sécuriser l'assignation des attributs](/doc/guide/form.model#securing-attribute-assignments), cette ligne de code rempli le modèle avec les données soumises par l'utilisateur. -La propriété `attributes` est défini par [CModel] qui attend un tableau de paires de noms-valeurs +La propriété `attributes` est définie par [CModel] qui attend un tableau de paires de noms-valeurs et assigne chaque valeur à l'attribut correspondant dans le modèle. Ainsi si `$_POST['LoginForm']` renvoie -un tableau, le code ci dessus serait équivalent au code beaucoup plus long ci dessous (chaque attribut doit +un tableau, le code ci-dessus serait équivalent au code beaucoup plus long ci-dessous (chaque attribut doit être présent dans le tableau): ~~~ @@ -58,11 +59,11 @@ $model->password=$_POST['LoginForm']['password']; $model->rememberMe=$_POST['LoginForm']['rememberMe']; ~~~ -> Note: Afin que `$_POST['LoginForm']` puisse retourner un tableau au lieu d'une chaine de +> Note: Afin que `$_POST['LoginForm']` puisse retourner un tableau au lieu d'une chaîne de caractères, on devra respecter une convention lors du nommage des noms des champs dans la vue. -En particulier, pour un champ correspondant à l'attribut `a` du modèle de classe `C`, on nomera celui ci `C[a]`. -Par example, on utiliserait `LoginForm[username]` comme nom pour le champ correspondant à l'attribut `username`. +En particulier, pour un champ correspondant à l'attribut `a` du modèle de classe `C`, on nommera celui-ci `C[a]`. +Par exemple, on utiliserait `LoginForm[username]` comme nom pour le champ correspondant à l'attribut `username`. Il reste maintenant à créer la vue `login` qui contiendra un formulaire HTML avec les champs nécessaires. -
$Id: form.action.txt 1837 2010-02-24 22:49:51Z qiang.xue $
+
$Id: form.action.txt 1838 2013-11-20 $
diff --git a/docs/guide/fr/form.model.txt b/docs/guide/fr/form.model.txt index 75c5c001b6..e73d7cee39 100644 --- a/docs/guide/fr/form.model.txt +++ b/docs/guide/fr/form.model.txt @@ -11,8 +11,8 @@ pour sauvegarder les entrées utilisateurs et pour les valider. Selon ce que l'on souhaite faire des données entrées par l'utilisateur, il est possible de créer deux types de modèles. Si les données sont utilisées puis immédiatement supprimées, un [form model](/doc/guide/basics.model) sera utilisé; si les données sont récupérées pour -etre enregistrées dans une base de données, un [active record](/doc/guide/database.ar) sera utilisé. -Ces deux types de modèles partagent la même classe mère [CModel] qui défini une interface commune +être enregistrées dans une base de données, un [active record](/doc/guide/database.ar) sera utilisé. +Ces deux types de modèles partagent la même classe mère [CModel] qui définit une interface commune requise pour les formulaires. > Note: Nous utiliserons principalement des modèles form dans les exemples suivants. @@ -21,8 +21,8 @@ Cependant, tout est applicable sur des modèles [active record](/doc/guide/datab Définir une classe pour un modèle --------------------------------- -Ci dessous nous créons une classe pour le modèle `LoginForm` afin de récupérer les données utilisateurs -sur une page de connection. Comme ces informations de connection ne seront utilisées que pour +Ci-dessous nous créons une classe pour le modèle `LoginForm` afin de récupérer les données utilisateurs +sur une page de connexion. Comme ces informations de connexion ne seront utilisées que pour authentifier l'utilisateur, sans être enregistrées, nous créerons `LoginForm` comme un modèle form. ~~~ @@ -38,20 +38,20 @@ class LoginForm extends CFormModel Trois attributs sont déclarés dans `LoginForm`: `$username`, `$password` et `$rememberMe`. Ils servent à stocker le nom d'utilisateur et le mot de passe entrés par l'utilisateur, ainsi que l'option pour rester connecté. -Comme `$rememberMe` à pour valeur par défaut `false`, l'option correspondante +Comme `$rememberMe` a pour valeur par défaut `false`, l'option correspondante quand le formulaire sera affiché sera décochée. -> Info: Plutot que d'appeler ces variables des propriétés, le terme +> Info: Au lieu d'appeler ces variables des propriétés, le terme d'attribut est préféré afin de les distinguer des propriétés normales. Un -attribut est une propriété qui est principalement utilisé pour stocker des +attribut est une propriété qui est principalement utilisée pour stocker des données venant d'entrées utilisateur ou de la base de données. Déclarer des règles de validation --------------------------------- -Une fois que l'utilisateur à envoyé son formulaire et que les données sont +Une fois que l'utilisateur a envoyé son formulaire et que les données sont stockées dans le modèle, il faut vérifier que ces données sont valides avant -de les utiliser. Pour cela un ensemble de règles sont appliquées à ces données +de les utiliser. Pour cela, un ensemble de règles sont appliquées à ces données afin de les valider. Pour spécifier ces règles, on utilise la méthode `rules()` qui retourne un tableau avec ces règles. @@ -83,9 +83,9 @@ class LoginForm extends CFormModel } ~~~ -Le code ci dessus déclare que `username` et `password` sont tout deux requis, +Le code ci-dessus déclare que `username` et `password` sont tous deux requis, `password` doit être un champ mot de passe qui correspondra avec le login, et `rememberMe` -doit etre un booléen. +doit être un booléen. Chaque règle retournée par `rules()` doit suivre le format suivant: @@ -94,18 +94,18 @@ Chaque règle retournée par `rules()` doit suivre le format suivant: array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...options supplémentaires) ~~~ -où `AttributeList` est une chaine de caractères d'attributs séparés par des virgules qui +où `AttributeList` est une chaîne de caractères d'attributs séparés par des virgules qui doivent être validés selon la règle; `Validator` spécifie quel type de validation doit être effectuée; le paramètre `on`, optionel, spécifie une liste de scénarios pour lesquels la règle doit être appliquée; et les options supplémentaires -sont des paires de noms-valeurs qui sont utilisés pour initialiser les valeurs des propriétés +sont des paires de noms-valeurs qui sont utilisées pour initialiser les valeurs des propriétés correspondantes du validateur. Il existe trois méthodes pour spécifier un `Validator` dans une règle de validation. La première, où un `Validator` peut être le nom d'une méthode dans une classe, -tel que `authenticate` dans l'exemple ci dessus. La méthode de validation doit suivre +telle que `authenticate` dans l'exemple ci-dessus. La méthode de validation doit suivre la signature suivante: ~~~ @@ -117,59 +117,59 @@ la signature suivante: public function ValidatorName($attribute,$params) { ... } ~~~ -Une deuxième où le `Validator` peut etre le nom d'une classe de validation. Quand la règle +Une deuxième, où le `Validator` peut être le nom d'une classe de validation. Quand la règle est appliquée, une instance de cette classe sera créée pour exécuter la validation. Les options supplémentaires dans la règle seront utilisées pour initialiser les attributs de cette instance. Une classe de validation doit hériter de [CValidator]. -Une troisième où le `Validator` sera un alias prédéfinit d'une classe de validation. Dans l'exemple -ci dessus, le nom `required` est un alias pour [CRequiredValidator] -qui s'assure que la valeur de l'attribut a validater n´est pas vide. Vous trouverez ci dessous +Une troisième, où le `Validator` sera un alias prédéfinit d'une classe de validation. Dans l'exemple +ci-dessus, le nom `required` est un alias de [CRequiredValidator] +qui garantit que la valeur de l'attribut à valider n'est pas vide. Vous trouverez ci-dessous la liste complète des alias de `Validator` prédéfinis: - - `boolean`: alias pour [CBooleanValidator], s'assure que l'attribut est -soit [CBooleanValidator::trueValue] ou [CBooleanValidator::falseValue]. + - `boolean`: alias de [CBooleanValidator], garantit que l'attribut est +soit [CBooleanValidator::trueValue] soit [CBooleanValidator::falseValue]. - - `captcha`: alias pour [CCaptchaValidator], s'assure que l'attribut est -égal à un code de vérification affiché dans un [CAPTCHA](http://en.wikipedia.org/wiki/Captcha). + - `captcha`: alias de [CCaptchaValidator], garantit que l'attribut est +égal au code de vérification affiché dans un [CAPTCHA](http://en.wikipedia.org/wiki/Captcha). - - `compare`: alias pour [CCompareValidator], s'assure que the attribute est égal à un autre attribut ou constante. + - `compare`: alias de [CCompareValidator], garantit que l'attribut est égal à un autre attribut ou à une constante. - - `email`: alias pour [CEmailValidator], s'assure que l'attribut est une -adresse email valide. + - `email`: alias de [CEmailValidator], garantit que l'attribut est une +adresse email syntaxiquement valide. - - `default`: alias pour [CDefaultValueValidator], assigne une valeur par défaut aux attributs. + - `default`: alias de [CDefaultValueValidator], assigne une valeur par défaut aux attributs. - - `exist`: alias pour [CExistValidator], s'assure que la valeur de l'attribut + - `exist`: alias de [CExistValidator], garantit que la valeur de l'attribut existe dans la colonne de la table spécifiée. - - `file`: alias pour [CFileValidator], s'assure que l'attribute contient + - `file`: alias de [CFileValidator], garantit que l'attribut contient le nome d'un fichier uploadé. - - `filter`: alias pour [CFilterValidator], transforme un attribut grâce à un filtre. + - `filter`: alias de [CFilterValidator], transforme un attribut grâce à un filtre. - - `in`: alias pour [CRangeValidator], s'assure que les données sont contenu dans une -liste de valeurs pré-specifiées. + - `in`: alias de [CRangeValidator], garantit que les données sont contenues dans une +liste de valeurs pré-définies. - - `length`: alias pour [CStringValidator], s'assure que la longueur de la valeur est -dans un certain interval. + - `length`: alias de [CStringValidator], garantit que la longueur de la valeur est comprise +dans un certain intervalle. - - `match`: alias pour [CRegularExpressionValidator], s'assure que la valeur satisfait -une expression régulière. + - `match`: alias de [CRegularExpressionValidator], garantit que la valeur satisfait +une expression régulière donnée. - - `numerical`: alias pour [CNumberValidator], s'assure que la valeur est un + - `numerical`: alias de [CNumberValidator], garantit que la valeur est un nombre valide. - - `required`: alias pour [CRequiredValidator], s'assure que l'attribut n´est + - `required`: alias de [CRequiredValidator], garantit que l'attribut n'est pas vide. - - `type`: alias pour [CTypeValidator], s'assure que l'attribut est d'un type de -donnée spécifique. + - `type`: alias de [CTypeValidator], garantit que l'attribut est du type de +donnée spécifié. - - `unique`: alias pour [CUniqueValidator], s'assure que la valeur est unique dans + - `unique`: alias de [CUniqueValidator], garantit que la valeur est unique dans une colonne de table. - - `url`: alias pour [CUrlValidator], s'assure que la valeur est une URL valide. + - `url`: alias de [CUrlValidator], garantit que la valeur est une URL syntaxiquement valide. Quelques exemples d'utilisation de ces `Validator` prédéfinis: @@ -181,7 +181,7 @@ array('username', 'required'), array('username', 'length', 'min'=>3, 'max'=>12), // quand dans un scénario d'inscription, password doit être égal à password2 array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), -// quand dans un scénario de connection, password doit être correspondre avec login +// quand dans un scénario de connexion, password doit être correspondre avec login array('password', 'authenticate', 'on'=>'login'), ~~~ @@ -215,16 +215,16 @@ foreach($_POST['LoginForm'] as $name=>$value) Il est crucial de déterminer quels attributs sont sains. Par exemple, si nous déclarons une clé primaire d'une table comme étant saine, alors un attaquant pourrait -avoir la possibilité de modifier cette clé et ainsi altérer des données qu´il ne devrait +avoir la possibilité de modifier cette clé et ainsi altérer des données qu'il ne devrait pas être autorisé à modifier. -La règle pour décider quels attributes sont sains est différent dans la version 1.0 -et 1.1. Nous allons les décrire séparemment. +La règle pour décider quels attributs sont sains est différente entres la version 1.0 +et la 1.1. Nous allons les décrire séparément. -###Attributs sains in 1.1 +###Attributs sains dans la version 1.1 -Dans la version 1.1, un attribut est considéré comme sain si il apparait dans une -règle de validation qui est appliqué dans le scénario courant. Par exemple, +Dans la version 1.1, un attribut est considéré comme sain s'il apparaît dans une +règle de validation qui est appliquée dans le scénario courant. Par exemple, ~~~ [php] @@ -232,7 +232,7 @@ array('username, password', 'required', 'on'=>'login, register'), array('email', 'required', 'on'=>'register'), ~~~ -Dans le code ci dessus, les attributs `username` et `password` sont requis dans le +Dans le code ci-dessus, les attributs `username` et `password` sont requis dans le scénario `login`, alors que les attributs `username`, `password` et `email` sont requis dans le scénario `register`. Ainsi, si une assignation massive est effectuée lors d'un scénario `login`, seulement `username` et `password` seront assignés massivement @@ -252,19 +252,19 @@ if(isset($_POST['User'])) $model->attributes=$_POST['User']; ~~~ -Pourquoi utiliser un telle politique pour déterminier si un attribut est sain ou non? -L´idée derrière ce principe est que si un attribut à déjà une ou plus règles de validation +Pourquoi utiliser une telle politique pour déterminier si un attribut est sain ou non? +L´idée derrière ce principe est que si un attribut à déjà une ou plusieurs règles de validation qui lui sont appliquées pour le valider, pourquoi se soucier encore de lui? Il est important de se rappeler que les règles de validation sont utilisées pour vérifier -les données provenant des utilisateurs plutot que les données générées par le code (par +les données provenant des utilisateurs plutôt que les données générées par le code (par exemple timestamp, clés primaires auto-générées). -Ainsi, ne PAS ajouter de règles de validation pour les attributes qui ne doivent pas +Ainsi, ne PAS ajouter de règles de validation pour les attributs qui ne doivent pas recevoir de données des utilisateurs. Parfois, il est utile de déclarer un attribut comme sain, bien que nous n´ayons aucune règle spécifique pour lui. Un exemple pourrait être un attribut représentant le contenu d'un -article qui pourrait recevoir potentiellement n´importe quelle valeur. Nous pouvont dans +article qui pourrait recevoir potentiellement n´importe quelle valeur. Nous pouvons dans ce cas utiliser la règle spéciale `safe` pour parvenir à nos fins: ~~~ @@ -284,17 +284,17 @@ Cette règle `unsafe` est rarement utilisée, et c´est une exception à notre p des attributs sains. -###Attributs sains in 1.0 +###Attributs sains dans la version 1.0 -Dans la version 1.0, la tache de décider si une donnée est saine ou non se fait +Dans la version 1.0, la tâche de décider si une donnée est saine ou non se fait sur la base de la valeur de retour de la méthode `safeAttributes` et du scénario spécifié. Par défaut, la méthode retourne toutes les variable publiques de [CFormModel] -en tant qu´attributs sains, alors qu´elle retourne toutes les colonnes de la table +en tant qu´attributs sains, alors qu'elle retourne toutes les colonnes de la table comme attributs sains à l'exception de la clé primaire pour [CActiveRecord]. Il est possible de surcharger cette méthode pour limiter les attributs sains selon les scénarios. Par exemple, un modèle utilisateur peut contenir de nombreux attributs, mais pour le scénario `login`, seuls les attributs `username` et `password` sont utiles. -Cette limite s'effectue comme suivant: +Cette limite s'effectue comme suit: ~~~ [php] @@ -308,13 +308,13 @@ public function safeAttributes() ~~~ Plus exactement, la valeur de retour de la méthode `safeAttributes` doit avoir la -structure suivant: +structure suivante: ~~~ [php] array( // ces attributs peuvent être assignés massivement pour tous les scénarios - // qui ne sont pas explicitement cités ci dessous + // qui ne sont pas explicitement cités ci-dessous 'attr1, attr2, ...', * // ces attributs peuvent être assignés massivement seulement pour le scénario 1 @@ -325,9 +325,9 @@ array( ) ~~~ -Si le modèle est indépendant de scénario (par exemple, si il est utilisé dans un -seul scénario, ou tous les scénarios partagent le même ensemble d'attributs sains), -la valeur de retour peut être simplifiée comme une simple chaine de caractères: +Si le modèle est indépendant d'un scénario (par exemple, s'il est utilisé dans un +seul scénario, ou si tous les scénarios partagent le même ensemble d'attributs sains), +la valeur de retour peut être simplifiée en une simple chaîne de caractères: ~~~ [php] @@ -335,7 +335,7 @@ la valeur de retour peut être simplifiée comme une simple chaine de caractère ~~~ Pour les données qui ne sont pas saines, il faut les assigner aux attributs -correspondants en utilisant les opérations d'assignation individuelle, comme suivant: +correspondants en utilisant les opérations d'assignation individuelles, comme suit: ~~~ [php] @@ -350,22 +350,22 @@ Déclenchement de la validation Une fois que les données soumises par l'utilisateur ont été assignées au modèle, il est possible d'appeler [CModel::validate()] pour déclencher le processus de validation des données. Cette méthode retourne une valeur indiquant si la validation s'est déroulée -sans erreur ou non. Pour les modèles [CActiveRecord], la validation sera aussi -automatiquement déclenchée à l'appel de la méthode [CActiveRecord::save()]. +sans erreur ou non. Pour les modèles [CActiveRecord], la validation sera automatiquement +déclenchée à l'appel de la méthode [CActiveRecord::save()]. Il est possible d'assigner un scénario avec la propriété [scenario|CModel::scenario] et d'indiquer quel ensemble de règles de validation doit être appliqué. -La validation est effectué sur la base des scénario. La propriété [scenario|CModel::scenario] +La validation est effectuée sur la base des scénarios. La propriété [scenario|CModel::scenario] spécifie dans quel scénario le modèle est actuellement utilisé et quel ensemble de règles de -validation doivent être utilisées. Par exemple, dans le scénario `login`, nous ne voulons +validation doivent être utilisé. Par exemple, dans le scénario `login`, nous ne voulons valider que les entrées `username` et `password` du modèle user; alors que dans le scénario `register`, nous voulons valider plus d'entrées, telles que `email`, `address`, etc. L'exemple suivant montre comment éffectuer une validation dans le scénario `register`: ~~~ [php] -// cré le modèle User dans un scénario register. Cela est équivalent à: +// crée le modèle User dans un scénario register. Cela est équivalent à: // $model=new User; // $model->scenario='register'; $model=new User('register'); @@ -375,9 +375,9 @@ $model->attributes=$_POST['User']; // effectue la validation if($model->validate()) // si les données sont valides - ... + ... else - ... + ... ~~~ Les scénarios applicables associés à une règle sont spécifiés au travers @@ -406,8 +406,8 @@ Récupération des erreurs de validation Une fois que la validation est finie, toutes les erreurs susceptibles d'avoir été rencontrées sont stockées dans l'objet. Il est alors possible de récupérer les messages d'erreurs en appelant [CModel::getErrors()] et [CModel::getError()]. La -différence entre ces deux méthodes est que la première renvoie *toutes* les erreurrs -pour un certain attribut du modèle alors que la seconde méthode renverra uniquement +différence entre ces deux méthodes est que la première renvoie *toutes* les erreurs +pour un certain attribut du modèle alors que la seconde méthode renvoie uniquement la *première* erreur. Libellés des attributs @@ -420,7 +420,8 @@ vue, il est plus pratique et cela offre plus de flexibilité de spécifier ces l dans le modèle. Par défaut, [CModel] retournera simplement le nom de l'attribut comme libellé. -Il est possible de personnaliser cela en surchargeant la méthode [attributeLabels()|CModel::attributeLabels]. Comme nous le verrons dans les sections suivantes, spécifier +Il est possible de personnaliser cela en surchargeant la méthode [attributeLabels()|CModel::attributeLabels]. +Comme nous le verrons dans les sections suivantes, spécifier des libellés au niveau du modèle permet de créer des formulaires plus rapidement. -
$Id: form.model.txt 2285 2010-07-28 20:40:00Z qiang.xue $
+
$Id: form.model.txt 2286 2013-11-20 $
diff --git a/docs/guide/fr/form.table.txt b/docs/guide/fr/form.table.txt index 7220212abf..41eb1d820d 100644 --- a/docs/guide/fr/form.table.txt +++ b/docs/guide/fr/form.table.txt @@ -3,21 +3,21 @@ Collecte d'entrées tabulaires Il peut être utile de récupérer des données utilisateur par lot (batch mode). L'utilisateur peut par exemple entrer des informations dans de multiples -instances de modèles et les soumettre d'une seule fois. On appelle cela une -entrée tabulaire car les champs sont souvent présentés dans une table HTML. +instances de modèles et les soumettre en une seule fois. On appelle cela une +*entrée tabulaire* car les champs sont souvent présentés dans une table HTML. Pour utiliser les entrées tabulaires, il faut d'abord créer ou remplir un -tableau d'instances de modèle. On récupère alors les données saisies par -l'utilisateur dans la variable `$_POST` et on les assignent à chaque modèle. -Une différence par rapport à la saisie pour un modèle unique est de récupérer -les données en utilisant `$_POST['ModelClass'][$i]` à la place de `$_POST['ModelClass']`. +tableau d'instances de modèle selon que nous voulons insérer ou mettre à jour les données. +On récupère alors les données saisies par l'utilisateur dans la variable `$_POST` et +on les assigne à chaque modèle. Une différence par rapport à la saisie pour un modèle unique +est que les données sont récupérées en utilisant `$_POST['ModelClass'][$i]` à la place de `$_POST['ModelClass']`. ~~~ [php] public function actionBatchUpdate() { // récupère les objets par lot - // on assume ici que chaque objet est de modèle 'Item' + // on assume ici que chaque objet est un modèle 'Item' $items=$this->getItemsToUpdate(); if(isset($_POST['Item'])) { @@ -60,11 +60,11 @@ Maintenant que l'action est prête, on peut travailler sur la vue ~~~ -Veuillez notez que `"[$i]name"` est utilisé ci dessus à la place de `"name"` +Veuillez notez que `"[$i]name"` est utilisé ci-dessus à la place de `"name"` en tant que deuxième paramètre lors de l'appel à [CHtml::activeTextField]. -Si il y des erreurs de validation, les champs de saisies correspondant -seront mis en valeurs (highlighted) automatiquement, de la même manière -que pour la saisie pour un modèle unique décrit précédemment. +S'il y des erreurs de validation, les champs de saisies correspondant +seront mis en évidence (highlighted) automatiquement, de la même manière +que décrit précédemment pour la saisie d'un modèle unique. -
$Id: form.table.txt 2232 2010-06-26 22:42:03Z qiang.xue $
+
$Id: form.table.txt 2234 2013-11-20 $
diff --git a/docs/guide/fr/form.view.txt b/docs/guide/fr/form.view.txt index 5a9ab8eb3b..520e85cf92 100644 --- a/docs/guide/fr/form.view.txt +++ b/docs/guide/fr/form.view.txt @@ -5,14 +5,14 @@ Ecrire la vue `login` est très simple. On débute avec un tag `form` dont l'attribut action sera l'URL de l'action `login` décrite précédemment. On rajoute ensuite les libellés (labels) et les champs (input) pour les attributs déclarés dans la classe `LoginForm`. A la fin on ajoute un bouton -de validation (submit). Tout cela est fait en HTML habituel. +d'envoi (submit). Tout cela est fait en HTML habituel. Yii fourni cependant des classes spéciales (helpers) facilitant la création de cette vue. Par exemple, on peut créer un champ de saisie de texte en utilisant [CHtml::textField()]; Ou encore pour créer une liste déroulante (drop-down list), on peut utiliser [CHtml::dropDownList()]. -> Info: On pourra se demander si les helpers sont vraiment utiles si ils requièrent +> Info: On pourra se demander si les helpers sont vraiment utiles s'ils requièrent > la même quantité de code comparé à du code HTML pur. En fait les helpers amènent > plus que du code HTML. Par exemple, le code suivant génère un champ texte qui > déclenchera la soumission du formulaire si sa valeur est éditée par un utilisateur. @@ -20,10 +20,9 @@ Ou encore pour créer une liste déroulante (drop-down list), on peut utiliser > [php] > CHtml::textField($name,$value,array('submit'=>'')); > ~~~ -> Sans les helpers, cela aurait nécessité plus de code et du javascript un peu partout. +> Sans les helpers, cela aurait nécessité plus de code et du javascript à bon escient. -Dans le code suivant, [CHtml] est utilisé pour créer le formulaire de connection (login). On assumera -que la variable `$model` représente une instance `LoginForm`. +Dans le code suivant, [CHtml] est utilisé pour créer le formulaire de connexion (login). On assumera que la variable `$model` représente une instance `LoginForm`. ~~~ [php] @@ -55,25 +54,23 @@ que la variable `$model` représente une instance `LoginForm`. ~~~ -Le code ci dessus génère un formulaire plus dynamique. Par example, +Le code ci-dessus génère un formulaire plus dynamique. Par exemple, [CHtml::activeLabel()] génère un libellé associé avec l'attribut du modèle. Si cet attribut à une erreur de saisie, la classe CSS du libellé changera à `error`, ce qui modifiera l'apparence du libellé avec les styles -CSS appropriés à une erreur. De facon similaire, [CHtml::activeTextField()] génère -un champ de saisie texte pour l'attribut du modèle et change sa classe CSS -en cas d'erreur. +CSS correspondant à une erreur. De façon similaire, [CHtml::activeTextField()] génère +un champ de saisie texte pour l'attribut du modèle et change sa classe CSS en cas d'erreur. -Si on utilise le fichier de style CSS `form.css` fourni par le script `yiic`, le formulaire -généré serait similaire à: +Si on utilise le fichier de style CSS `form.css` fourni par le script `yiic`, le formulaire généré est similaire à: -![Page de connection](login1.png) +![Page de connexion](login1.png) -![Page de connection avec erreurs](login2.png) +![Page de connexion avec erreurs](login2.png) Depuis la version 1.1.1, un nouveau widget appelé [CActiveForm] est fourni -afin de faciliter la création de formulaire. Ce widget est capable d'effectuer -une validation consistante aussi bien du coté client que du coté serveur. Le code -ci dessus peut être réécrit comme suivant en utilisant [CActiveForm]: +afin de faciliter la création de formulaires. Ce widget est capable d'effectuer +une validation consistante aussi bien du côté client que du côté serveur. Le code +ci-dessus peut être réécrit comme suit en utilisant [CActiveForm]: ~~~ [php] @@ -105,4 +102,4 @@ ci dessus peut être réécrit comme suivant en utilisant [CActiveForm]: ~~~ -
$Id: form.view.txt 1751 2010-01-25 17:21:31Z qiang.xue $
+
$Id: form.view.txt 1752 2013-11-19 $
diff --git a/docs/guide/fr/index.txt b/docs/guide/fr/index.txt index 54fc0924d3..4370604e66 100644 --- a/docs/guide/fr/index.txt +++ b/docs/guide/fr/index.txt @@ -3,7 +3,7 @@ Le Guide Définitif de Yii Tous droits réservés. -2008-2009 © Yii Software LLC. +2008-2013 © Yii Software LLC. -
$Id: index.txt 687 $
+
$Id: index.txt 688 $
diff --git a/docs/guide/fr/quickstart.first-app.txt b/docs/guide/fr/quickstart.first-app.txt index 3739c7adfc..4f09f99bcd 100644 --- a/docs/guide/fr/quickstart.first-app.txt +++ b/docs/guide/fr/quickstart.first-app.txt @@ -1,248 +1,219 @@ -Créer sa première applicatin Yii -================================ - -Pour se faire la main sur Yii, nous allons décrire dans cette section comment créér -notre première application avec le framework Yii. Nous allons utiliser le puissant outil -`yiic` qui peut automatiser la création du code pour certaines taches. Par convention, -nous supposons que `YiiRoot` est le répertoire où Yii est installé, and `WebRoot` est -la racine Web du serveur. - -Lancer `yiic` sur la ligne de commande comme suit : - -~~~ -% YiiRoot/framework/yiic webapp WebRoot/testdrive -~~~ - -> Note|Note: Sur Mac OS, Linux ou Unix, il sera peut être nécessaire de -> modifier les permissions sur le fichier `yiic` pour le rendre exécutable. -> Il est aussi possible de lancer l'outil comme suit : -> -> ~~~ -> % cd WebRoot/testdrive -> % php YiiRoot/framework/yiic.php webapp WebRoot/testdrive -> ~~~ - -Cette commande va créer un squelette d'application Yii sous le répertoire -`WebRoot/testdvive`. L'application a une structure de répertoire qui -est nécessaire à la plupart des applications `Yii` - -Sans écrire une seule ligne de code, nous pouvons tester notre première application -Uii, en accédant à l'url suivante dans le navigateur : - -~~~ -http://hostname/testdrive/index.php -~~~ - -Comme nous pouvons le constater, l'application a trois pages: la page d'accueil, -la page de contact et la page de login. La page d'accueil nous montre des informations -sur l'application, ainsi que sur l'état de l'utilisateur connecté. La page de contact -affiche un formulaire de contact que les utilisateurs peuvent remplir, et la page de -login permet aux utilisateurs de d'authentifier avant d'accéder à un contenu privé. -Les copies d'écran suivantes nous montrent plus de details. - - -![Page d'accueil](first-app1.png) - -![Page Contact](first-app2.png) - -![Page de contact avec des erreurs de saisie](first-app3.png) - -![Page de contact envoyée avec succès](first-app4.png) - -![Page de login](first-app5.png) - - -Le schéma suivant montre la structure des répertoires de notre applications. -Veuillez consulter la page [Conventions](/doc/guide/basics.convention#directory) pour -de plus amples informations à propos de cette structure. - -~~~ -testdrive/ - index.php Point d'entrée de l'application. - assets/ Contient les fichiers de ressources publiés - css/ Contient les fichiers CSS de l'application - images/ Contient les images - themes/ Contient les différents thèmes de l'application - protected/ Contient les fichiers protégés de l'application - yiic L'outil en ligne de commande 'yiic' - yiic.bat L'outil en ligne de commande 'yiic' pour Windows - commands/ Contient les commandes personnalisées pour l'outil 'yiic' - shell/ Contient les commandes personnalisées pour l'outil 'yiic shell' - components/ Contient les composants utilisateur - MainMenu.php Le widget 'MainMenu' - Identity.php La class 'Identity' utilisée pour l'authentification - views/ Contient les fichiers 'Vue' des widgets - mainMenu.php Le fichier Vue pour le widget 'MainMenu" - config/ Contient les fichiers de configuration - console.php Le fichier de configuration pour l'application en ligne de commande - main.php Le fichier de confiruration pour l'application Web - controllers/ Contient les fichiers des Controlleurs - SiteController.php Le controlleur par défaut - extensions/ Contient les extensions tierces - messages/ Contient les messages traduits - models/ Contient les fichiers des Modèles - LoginForm.php Le modèle de type Formulaire pour l'action 'Login' - ContactForm.php Le modèle de type Formulaire pour l'action 'Contact' - runtime/ Contient les fichiers temporaires générés par l'application - views/ Contient les fichiers Vue des controlleurs et Layout - layouts/ Contient les fichiers Vue des Layout - main.php Le 'layout' par défaut pour toutes les vues - site/ Contient les fichiers Vue pour le controlleur 'site' - contact.php La vue pour l'action 'Contact' - index.php La vue pour l'action 'index' - login.php La vue pour l'action 'login' - system/ Contient les fichiers pour les vues system -~~~ - -Connexions aux bases de données -------------------------------- - -La plupart des applications web sont gérées par une base de données. -Notre application de test n'est pas une exception. Pour utiliser une base de -donnée, nous devons auparavant signaler à l'application comment se connecter à -celle-ci. Pour cela, il faut modifier le fichier de configuration de l'application -`WebRoot/testdrive/protected/config/main.php` comme ceci : - -~~~ -[php] -return array( - ...... - 'components'=>array( - ...... - 'db'=>array( - 'connectionString'=>'sqlite:protected/data/source.db', - ), - ), - ...... -); -~~~ - -Dans cet exemple, nous ajoutons une entrée nommée `db` aux `composants`, qui -permet à l'application de se conencter à la base de donnée SQLlite -`WebRoot/testdrive/protected/data/source.db` quand celà est nécessaire. - -> Note|Note : Pour utiliser les fonctionnalités de base de données de Yii, il -> est nécessaire d'activer l'extension PDO dans PHP, ainsi que les différents drivers -> PDO spécifiques à chaque base de donnée. Pour notre application de test, nous -> avons besoin que les extensions `php_pdo` et `php_pdo_sqlite` soient activées. - -Pour finir, nous devons préparer une base de donnée SQLite. En utilisant les outils -d'administration SQLite, nous pouvons créer une base de donnée avec le modèle suivant : - -~~~ -[sql] -CREATE TABLE User ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - username VARCHAR(128) NOT NULL, - password VARCHAR(128) NOT NULL, - email VARCHAR(128) NOT NULL -); -~~~ - -> Note: Si vous utilisez MySQL, vous devez remplacer `AUTOINCREMENT` -> par `AUTO_INCREMENT` dans le code SQL ci-dessus. - -Par souci de simplification, nous créeons seulement une table `User` dans notre -base. Le fichier de base de donnée SQLite est sauvé sous le nom - -`WebRoot/testdrive/protected/data/source.db`. Notez que le nom de fichier ainsi -que le répertoire qui le contient doivent être accessibles en écriture par l'utilisateur -exécutant le serveur web. - - -Implémentations des opérations CRUD ------------------------------------ - -Maintenant, la partie amusante. Nous aimerions implémenter les opérations CRUD -(Create, Read, Update and Delete, soit Création, Lecture, Modification et Suppression) -sur la table `User` que nous venons de créer. C'est une opération nécessaire la -plupart du temps. - -Plutot que d'écrire du code, nous allons encore nous servir du puissant outil `yiic`, -afin de générer automatiquement le code. Ce processus est aussi connu sous le nom de -*scaffolding*. Ouvrez une fenêtre de ligne de commande, et exécutez les commandes suivantes : - -~~~ -% cd WebRoot/testdrive -% protected/yiic shell -Yii Interactive Tool v1.0 -Please type 'help' for help. Type 'exit' to quit. ->> model User - generate User.php - -The 'User' class has been successfully created in the following file: - D:\wwwroot\testdrive\protected\models\User.php - -If you have a 'db' database connection, you can test it now with: - $model=User::model()->find(); - print_r($model); - ->> crud User - generate UserController.php - mkdir D:/wwwroot/testdrive/protected/views/user - generate create.php - generate update.php - generate list.php - generate show.php - -Crud 'user' has been successfully created. You may access it via: -http://hostname/path/to/index.php?r=user -~~~ - -Ci-dessus, nous utilisons la commande `yiic shell` pour interagir avec notre -application. Au prompt, nous exécutons 2 sous-commandes : `model User`, et `crud User`. -La première genère une class "Model" pour la table `User`, et la seconde lit ce modèle -pour générer le code qui implémente les traitements CRUD. - -> Note|Note : Si vous rencontrez des erreurs comme "...could not find driver", alors -> que le script de test des pré-requis signale que PDO et les drivers associés sont -> bien activés, vous pouvez essayer de lancer l'outil `yiic` de la manière suivante : -> -> ~~~ -> % php -c path/to/php.ini protected/yiic.php shell -> ~~~ -> -> où `path/to/php.ini` représente le chemin d'accès au fichier php.ini - -Jetons maintenant un oeil à notre application par cette URL : - -~~~ -http://hostname/testdrive/index.php?r=user -~~~ - -Cela va afficher la liste des entrées dans la table `User`. Etant donné que -notre table est vide, rien n'apparaîtra pour le moment. - -Cliquez sur le lien `New User` sur la page. Si nous n'étions pas encore connecté, -nous allons être redirigés vers la page de login. Après s'être connecté, un formulaire -contenant tous les champs permettant la création d'un utilisateur sera affiché. -Completez le formulaire, et cliquez sur le bouton `Create`. Si une erreur de saisie -s'est glissée dans le formulaire, un message d'erreur sera affiché, nous empêchant -alors de sauvegarer l'entrée. En revenant à la liste des utilisateurs, nous devrions -alors voir le nouvel utilisateur que nous avons créé. - -Répetez ces étapes pour ajouter plus d'utilisateur. Notez que la liste d'utilisateur -sera automatiquement paginée s'il y a trop d'utilisateur à afficher sur une seule -page. - -Si nous nous connectons en tant qu'administrateur en utilisant `admin/admin`, nous -pouvons visiter l'a page d'administration à l'URL suivante : - -~~~ -http://hostname/testdrive/index.php?r=user/admin -~~~ - -Cette page affiche une jolie table des utilisateurs. Il est possible de cliquer -sur les entêtes de la table afin de la trier selon la colonne correspondante. Et, -de la même manière que la liste précédente, cette table sera paginée si le nombre -d'entrées est trop important. - -Toutes ces fonctionnalités ont été réalisées sans écrire une seule ligne de code ! - -![Page d'administration des utilisateurs](first-app6.png) - -![Page de création d'un nouvel utilisateur](first-app7.png) - - - -
$Id: quickstart.first-app.txt 1264 $
\ No newline at end of file +Créer sa première application Yii +================================ + +Pour se faire la main avec Yii, nous allons décrire dans cette section comment créer votre première application avec le framework Yii. Vous allez pour cela utiliser le puissant outil `yiic` qui peut automatiser la création du code pour certaines tâches. Par convention, nous supposons que `YiiRoot` est le répertoire où Yii est installé, et `WebRoot` est la racine Web du serveur. + +Lancez `yiic` sur la ligne de commande comme suit : + +~~~ +% YiiRoot/framework/yiic webapp WebRoot/testdrive +~~~ + +> Note|Note: Sur Mac OS, Linux ou Unix, il sera peut être nécessaire de +> modifier les permissions sur le fichier `yiic` pour le rendre exécutable. +> Il est aussi possible de lancer l'outil comme suit : +> +> ~~~ +> % cd WebRoot/testdrive +> % php YiiRoot/framework/yiic.php webapp WebRoot/testdrive +> ~~~ + +Cette commande va créer un squelette d'application Yii sous le répertoire +`WebRoot/testdrive`. L'application comporte une structure de répertoire nécessaire à la plupart des applications `Yii` + +Sans écrire une seule ligne de code, vous pouvez tester votre première application Yii, en accédant à l'url suivante dans le navigateur : + +~~~ +http://WebRoot/testdrive/index.php +~~~ + +Comme vous pouvez le constater, l'application a trois pages: la page d'accueil, +la page de contact et la page de login. La page d'accueil vous montre des informations sur l'application, ainsi que sur l'état de l'utilisateur connecté. La page de contact affiche un formulaire de contact que les utilisateurs peuvent remplir, et la page de login permet aux utilisateurs de s'authentifier avant d'accéder à un contenu privé. +Les copies d'écran suivantes vous montrent plus de détails. + + +![Page d'accueil](first-app1.png) + +![Page Contact](first-app2.png) + +![Page de contact avec des erreurs de saisie](first-app3.png) + +![Page de contact envoyée avec succès](first-app4.png) + +![Page de login](first-app5.png) + + +Le schéma suivant montre la structure des répertoires de votre application. +Veuillez consulter la page [Conventions](/doc/guide/basics.convention#directory) pour de plus amples informations à propos de cette structure. + +~~~ +testdrive/ + index.php Point d'entrée de l'application. + assets/ Contient les fichiers de ressources publiés + css/ Contient les fichiers CSS de l'application + images/ Contient les images + themes/ Contient les différents thèmes de l'application + protected/ Contient les fichiers protégés de l'application + yiic L'outil en ligne de commande 'yiic' + yiic.bat L'outil en ligne de commande 'yiic' pour Windows + commands/ Contient les commandes personnalisées pour l'outil 'yiic' + shell/ Contient les commandes personnalisées pour l'outil 'yiic shell' + components/ Contient les composants utilisateur + MainMenu.php Le widget 'MainMenu' + Identity.php La class 'Identity' utilisée pour l'authentification + views/ Contient les fichiers 'Vue' des widgets + mainMenu.php Le fichier Vue pour le widget 'MainMenu" + config/ Contient les fichiers de configuration + console.php Le fichier de configuration pour l'application en ligne de commande + main.php Le fichier de configuration pour l'application Web + controllers/ Contient les fichiers des Controlleurs + SiteController.php Le contrôleur par défaut + extensions/ Contient les extensions tierces + messages/ Contient les messages traduits + models/ Contient les fichiers des Modèles + LoginForm.php Le modèle de type Formulaire pour l'action 'Login' + ContactForm.php Le modèle de type Formulaire pour l'action 'Contact' + runtime/ Contient les fichiers temporaires générés par l'application + views/ Contient les fichiers Vue des contrôleurs et Layout + layouts/ Contient les fichiers Vue des Layout + main.php Le 'layout' par défaut pour toutes les vues + site/ Contient les fichiers Vue pour le contrôleur 'site' + contact.php La vue pour l'action 'Contact' + index.php La vue pour l'action 'index' + login.php La vue pour l'action 'login' + system/ Contient les fichiers pour les vues système +~~~ + +Connexions aux bases de données +------------------------------- + +La plupart des applications web sont gérées par une base de données. +Notre application de test ne fait pas exception. Pour utiliser une base de +données, nous devons auparavant signaler à l'application comment se connecter à la base de données. Pour cela, il faut modifier le fichier de configuration de l'application +`WebRoot/testdrive/protected/config/main.php` comme ceci : + +~~~ +[php] +return array( + ...... + 'components'=>array( + ...... + 'db'=>array( + 'connectionString'=>'sqlite:protected/data/source.db', + ), + ), + ...... +); +~~~ + +Dans cet exemple, nous ajoutons une entrée nommée `db` aux `components` (composants), qui indique à l'application de se connecter à la base de données SQLlite +`WebRoot/testdrive/protected/data/source.db` quand cela est nécessaire. + +> Note|Note : Pour utiliser les fonctionnalités de base de données de Yii, il +> est nécessaire d'activer l'extension PDO dans PHP, ainsi que les différents drivers +> PDO spécifiques à chaque base de données. Pour votre application de test, vous +> avez besoin que les extensions `php_pdo` et `php_pdo_sqlite` soient activées. + +Pour finir, nous devons préparer une base de donnée SQLite. En utilisant les outils d'administration SQLite, vous pouvez créer une table dans la base de données avec le modèle suivant : + +~~~ +[sql] +CREATE TABLE User ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + username VARCHAR(128) NOT NULL, + password VARCHAR(128) NOT NULL, + email VARCHAR(128) NOT NULL +); +~~~ + +> Note: Si vous utilisez MySQL, vous devez remplacer `AUTOINCREMENT` +> par `AUTO_INCREMENT` dans le code SQL ci-dessus. + +Par souci de simplification, seule une table appelée `User` est créée dans la base. Le fichier de base de données SQLite est sauvé sous le nom + +`WebRoot/testdrive/protected/data/source.db`. Notez que le nom de fichier ainsi +que le répertoire qui le contient doivent être accessibles en écriture par l'utilisateur exécutant le serveur web. + + +Implémentations des opérations CRUD +----------------------------------- + +Maintenant, la partie amusante! Vous allez implémenter les opérations CRUD sur la table `User` que vous venez de créer (CRUD signifie Create, Read, Update et Delete, i.e. Création, Lecture, Modification et Suppression). Les opérations CRUD sont le fondement de la manipulation des données et donc nécessaires la plupart du temps. + +Plutôt que d'écrire du code qui implémente ces opérations CRUD, vous allez encore vous servir du puissant outil `yiic`, afin de générer automatiquement ce code. Ce processus est aussi connu sous le nom de *scaffolding* (échafaudage). Ouvrez une fenêtre de ligne de commande, et exécutez les commandes suivantes : + +~~~ +% cd WebRoot/testdrive +% protected/yiic shell +Yii Interactive Tool v1.0 +Please type 'help' for help. Type 'exit' to quit. +>> model User + generate User.php + +The 'User' class has been successfully created in the following file: + D:\wwwroot\testdrive\protected\models\User.php + +If you have a 'db' database connection, you can test it now with: + $model=User::model()->find(); + print_r($model); + +>> crud User + generate UserController.php + mkdir D:/wwwroot/testdrive/protected/views/user + generate create.php + generate update.php + generate list.php + generate show.php + +Crud 'user' has been successfully created. You may access it via: +http://hostname/path/to/index.php?r=user +~~~ + +Ci-dessus, c'est la commande `yiic shell` qui est utilisée pour interagir avec votre +application. Au prompt, nous exécutons 2 sous-commandes : `model User`, et `crud User`. +La première génère une classe "Model" pour la table `User`, et la seconde lit ce modèle pour générer le code qui implémente les opérations CRUD. + +> Note|Note : Si vous rencontrez des erreurs comme "...could not find driver", alors +> que le script de test des pré-requis signale que PDO et les drivers associés sont +> bien activés, vous pouvez essayer de lancer l'outil `yiic` de la manière suivante : +> +> ~~~ +> % php -c path/to/php.ini protected/yiic.php shell +> ~~~ +> +où `path/to/php.ini` représente le chemin d'accès au fichier php.ini + +Jetons maintenant un œil à notre application qui est à cette URL : + +~~~ +http://WebRoot/testdrive/index.php?r=user +~~~ + +Cela va afficher la liste des entrées contenues dans la table `User`. Etant donné que notre table est vide, rien n'apparaîtra pour le moment. + +Cliquez sur le lien `New User` sur la page. Si vous n'étiez pas encore connecté, vous allez être redirigé vers la page de login. Après s'être connecté, un formulaire contenant tous les champs permettant la création d'un utilisateur sera affiché. +Complétez le formulaire, et cliquez sur le bouton `Create`. Si une erreur de saisie +s'est glissée dans le formulaire, un message d'erreur sera affiché, vous empêchant alors de sauvegarder l'entrée. En revenant à la liste des utilisateurs, vous devriez alors voir le nouvel utilisateur que vous avez créé. + +Répétez ces étapes pour ajouter autant d'utilisateurs que vous voulez. Notez que la liste des utilisateurs sera automatiquement paginée s'il y a de nombreux utilisateurs à afficher sur une seule page. + +Si vous vous connectez en tant qu'administrateur en utilisant `admin/admin`, vous pouvez visiter la page d'administration à l'URL suivante : + +~~~ +http://WebRoot/testdrive/index.php?r=user/admin +~~~ + +Cette page affiche une jolie table des utilisateurs. Il est possible de cliquer +sur les entêtes de la table afin de la trier selon la colonne correspondante. Et, +de la même manière que la liste précédente, cette table est paginée si le nombre +d'entrées est trop important. + +Toutes ces fonctionnalités ont été réalisées sans écrire une seule ligne de code! + +![Page d'administration des utilisateurs](first-app6.png) + +![Page de création d'un nouvel utilisateur](first-app7.png) + + + +
$Id: quickstart.first-app.txt 1267 2013-11-20 $
diff --git a/docs/guide/fr/quickstart.installation.txt b/docs/guide/fr/quickstart.installation.txt index 8bc24d38b8..c61e254125 100644 --- a/docs/guide/fr/quickstart.installation.txt +++ b/docs/guide/fr/quickstart.installation.txt @@ -22,8 +22,8 @@ de la configuration du serveur à l'URL : http://hostname/path/to/yii/requirements/index.php ~~~ -La condition minimum requise par Yii est d'avoir un serveur Web qui supporte PHP 5.1.0 ou supérieur. +La condition minimale requise par Yii est d'avoir un serveur Web qui supporte PHP 5.1.0 ou supérieur. Yii a été testé avec le [serveur HTTP Apache](http://httpd.apache.org/) sur Windows et Linux. -Yii devrait également fonctionner sur les autres serveurs et os supportant PHP 5. +Yii devrait également fonctionner sur les autres serveurs et OS supportant PHP 5. -
$Id: quickstart.installation.txt 1906 $
+
$Id: quickstart.installation.txt 1907 $
diff --git a/docs/guide/fr/quickstart.what-is-yii.txt b/docs/guide/fr/quickstart.what-is-yii.txt index 8628fdda98..89786b1407 100644 --- a/docs/guide/fr/quickstart.what-is-yii.txt +++ b/docs/guide/fr/quickstart.what-is-yii.txt @@ -1,12 +1,11 @@ Qu'est-ce que Yii ================= -Yii est un framework PHP basé sur des composants ultra performant qui a été développé -pour créer des application Web de grande qualité. -Yii a été conçu autour de composants dans le but d'accélerer le développement +Yii est un framework PHP basé sur des composants ultra performants qui a été développé pour créer des applications Web de grande qualité. +Yii a été conçu autour de composants dans le but d’accélérer le développement d'applications Web en simplifiant la réutilisation de code existant. -Le nom Yii (à prononcer `Yee` ou `[ji:]`) se veut être un synonyme de *facile*, *efficace* et *extensible*. +Le nom Yii (se prononce `Yee` ou `[ji:]`) se veut être un synonyme de *facile*, *efficace* et *extensible*. Pré-requis ---------- @@ -18,7 +17,7 @@ Pour les développeurs qui veulent utiliser Yii, la connaissance des principes de base de la programmation orientée-objet (OOP) est un plus car Yii est un pur framework OOP. -Pour quel usage Yii est il optimal? +Pour quel usage Yii est-il optimal? ----------------------------------- Yii est un framework générique conçu pour faciliter la programmation web. @@ -28,7 +27,7 @@ le rendent particulièrement adapté au développement d'applications a forte audience telles que des portails, des forums, des sites de gestion de contenu (CMS), des sites e-commerce, etc. -Comment se positionne Yii vis-à-vis des autres Frameworks? +Comment se positionne Yii vis-à-vis des autres frameworks? -------------------------------------------------------- Comme la plupart des frameworks PHP, Yii est basé sur le modèle MVC. @@ -36,8 +35,7 @@ Comme la plupart des frameworks PHP, Yii est basé sur le modèle MVC. Les points forts de Yii sont l'efficacité, la richesse fonctionnelle et une documentation claire. Yii a été conçu dès le départ pour permettre le développement d'applications Web de grande qualité. Ce n'est ni le dérivé -d'un projet existant ni l'aggrégation de composants tiers. Ce framework résulte -de la riche expérience des auteurs ainsi que de l'analyse des meilleurs framework -et applications Web du marché. +d'un projet existant ni l’agrégation de composants tiers. Ce framework résulte +de la riche expérience des auteurs ainsi que de l'analyse des meilleurs framework et applications Web du marché. -
$Id: quickstart.what-is-yii.txt 1906 $
+
$Id: quickstart.what-is-yii.txt 1907 $
diff --git a/docs/guide/ja/basics.application.txt b/docs/guide/ja/basics.application.txt index 876f8b02d1..6f4cfcd3c4 100644 --- a/docs/guide/ja/basics.application.txt +++ b/docs/guide/ja/basics.application.txt @@ -170,7 +170,7 @@ Yii は、ウェブアプリケーションに共通な機能を提供するた 0. [CApplication::preinit()] によりアプリケーションをプレ初期化します; - 1. クラス自動ローダとエラー処理を準備します; + 1. エラー処理をセットアップします; 2. コアアプリケーションコンポーネントを登録します; diff --git a/docs/guide/ja/caching.data.txt b/docs/guide/ja/caching.data.txt index 9f567ea7ea..ad68dee2ef 100644 --- a/docs/guide/ja/caching.data.txt +++ b/docs/guide/ja/caching.data.txt @@ -11,8 +11,8 @@ Yii::app()->cache->set($id, $value); ~~~ -キャッシュされたデータは、キャッシングポリシー (たとえば、キャッシュ容量がいっぱいになれば、一番古いデータが削除されます) のために、キャッシュが消されない限りずっと残ります。 -この挙動を変えるために、[set()|CCache::set] を呼ぶときに、有効期限パラメータを指定し、一定の期間の後、キャッシュが削除されるようにする事もできます。 +キャッシュされたデータは、何らかのキャッシングポリシー (たとえば、キャッシュ容量がいっぱいになれば、一番古いデータが削除される、など) によって削除されない限りは、ずっと残ります。 +この挙動を変えるために、[set()|CCache::set] を呼ぶときに、有効期限パラメータを指定して、長くとも一定の期間の後には、必ずキャッシュが削除されるようにする事もできます。 ~~~ [php] @@ -20,7 +20,7 @@ Yii::app()->cache->set($id, $value); Yii::app()->cache->set($id, $value, 30); ~~~ -後で (同じウェブリクエスト、あるいは、別のウェブリクエストの中で) この変数にアクセスする必要が生じときに、その ID を指定して [get()|CCache::get] を呼ぶと、キャッシュから変数を読み込むことが出来ます。 +後で (同じウェブリクエスト、あるいは、別のウェブリクエストの中で) この変数にアクセスする必要が生じたときに、その ID を指定して [get()|CCache::get] を呼ぶと、キャッシュから変数を読み込むことが出来ます。 返り値が false である場合は、変数の値がキャッシュの中に無いことを意味しますので、値を再生成する必要があります。 ~~~ @@ -71,7 +71,7 @@ MemCache や APC のようなある種のキャッシュストレージにおい Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName')); ~~~ -今、[get()|CCache::get] を呼び出してキャッシュから `$value` を取り出そうとすると、依存関係が調査されます。 +このようにすると、[get()|CCache::get] を呼び出してキャッシュから `$value` を取り出そうとしたときに依存関係が調査されます。 そして、依存関係が変更されていれば、データを再生成する必要があることを知らせるために、[get()|CCache::get] は false の値を返します。 以下は、利用可能なキャッシュ依存関係の概要です: @@ -123,8 +123,8 @@ $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll( このチェックは、以下の3つの条件をチェックすることによって行なわれます。 - キャッシュが SQL 文によってインデックスされるエントリをもっているかどうか。 -- キャッシュエントリが有効期限切れになっていないかどうか (最初にキャッシュに格納されてから1000秒以内)。 -- 依存性が変更されていないか (`update_time` の最大値がクエリ結果が格納された時と同じ値かどうか) +- キャッシュエントリが有効期限切れになっていないかどうか (最初にキャッシュに格納されてから1000秒未満)。 +- 依存性が変更されていないか (`update_time` の最大値がクエリ結果がキャッシュに格納された時と同じ値かどうか) もし上記すべての条件が満たされていれば、キャッシュされた結果が直接キャッシュから返されます。 そうでなければ、SQL 文が DB サーバへ送信されて実行され、対応する結果がキャッシュに格納されて返されます。 @@ -214,5 +214,3 @@ $posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(a ある種のキャッシュストレージはサイズに制約があります。 例えば、memcache では、各エントリのサイズは 1MB が上限値です。 そのため、クエリの結果のサイズがこの制約を越える場合、キャッシュは失敗します。 - -
$Id$
diff --git a/docs/guide/ja/caching.overview.txt b/docs/guide/ja/caching.overview.txt index b0a8b0afd0..0b40dfcd24 100644 --- a/docs/guide/ja/caching.overview.txt +++ b/docs/guide/ja/caching.overview.txt @@ -1,10 +1,10 @@ キャッシュ概要 ======= -キャッシュはウェブアプリケーションのパフォーマンスを改善する、安価で効果的な方法です。 -比較的静的なデータをキャッシュし、リクエストがあった際にそのキャッシュを利用することで、データを生成するための時間を抑える事ができます。 +キャッシュはウェブアプリケーションのパフォーマンスを改善する安価で効果的な方法です。 +比較的静的なデータをキャッシュに保存し、リクエストがあった時にそれをキャッシュから提供することによって、データを生成するための時間を節約する事ができます。 -Yii におけるキャッシュの利用は、主として、アプリケーション初期設定とキャッシュアプリケーションコンポーネントへのアクセスに関係します。 +Yii においてキャッシュを利用するためには、主として、キャッシュアプリケーションコンポーネントを設定してアクセスすることが必要です。 次のアプリケーション初期設定は、2台のキャッシュサーバで memcache を使用するキャッシュコンポーネントを指定しています。 ~~~ @@ -48,9 +48,9 @@ Yii は、異なるメディアにキャッシュデータを保存すること - [CZendDataCache]: キャッシュメディアとして [Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) を使用します。 -+ - [CWinCache]: PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) 拡張を使用します。 -+ ([PHP 用の Windows キャッシュ](http://php.net/manual/ja/book.wincache.php) も参照して下さい。) -+ + - [CWinCache]: PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) 拡張を使用します。 + ([PHP 用の Windows キャッシュ](http://php.net/manual/ja/book.wincache.php) も参照して下さい。) + - [CFileCache]: キャッシュデータを保存するのにファイルを使用します。これはページのような大きなかたまりのデータに特に適しています。 - [CDummyCache]: なにもキャッシュを行わないダミーキャッシュを提供します。 diff --git a/docs/guide/ja/database.ar.txt b/docs/guide/ja/database.ar.txt index b616ad2f3b..2bd7ec69f5 100644 --- a/docs/guide/ja/database.ar.txt +++ b/docs/guide/ja/database.ar.txt @@ -69,6 +69,7 @@ AR に対するサポートは、DBMS によって制限されます。 現在、以下の DBMS だけがサポートされています。 - [MySQL 4.1 以降](http://www.mysql.com) + - [MariaDB](https://mariadb.com) - [PostgreSQL 7.3 以降](http://www.postgres.com) - [SQLite 2 または 3](http://www.sqlite.org) - [Microsoft SQL Server 2000 以降](http://www.microsoft.com/sqlserver/) diff --git a/docs/guide/ja/database.dao.txt b/docs/guide/ja/database.dao.txt index 6d4e172438..64c385b14c 100644 --- a/docs/guide/ja/database.dao.txt +++ b/docs/guide/ja/database.dao.txt @@ -39,7 +39,7 @@ DSN のフォーマットは、使用する PDO データベースドライバ 以下に、一般に用いられる DSN フォーマットのリストを示します: - SQLite: `sqlite:/path/to/dbfile` - - MySQL: `mysql:host=localhost;dbname=testdb` + - MySQL/MariaDB: `mysql:host=localhost;dbname=testdb` - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb` - SQL Server: `mssql:host=localhost;dbname=testdb` - Oracle: `oci:dbname=//localhost:1521/testdb` diff --git a/docs/guide/ja/database.migration.txt b/docs/guide/ja/database.migration.txt index 77cdb922e4..80e843fcc9 100644 --- a/docs/guide/ja/database.migration.txt +++ b/docs/guide/ja/database.migration.txt @@ -187,7 +187,10 @@ Yii はマイグレーションを実行する時に、DB トランザクショ > Note|注意: すべての DBMS がトランザクションをサポートしている訳ではありません。 > また、DB クエリの中には、トランザクションの中に入れることが出来ないものもあります。 > その場合には、代りに `up()` または `down()` を実装しなければなりません。 -> また、MySQL の場合、[暗黙のコミット](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) を引き起こす SQL 文がありますので、注意して下さい。 +> また、MySQL と MariaDB の場合、[暗黙のコミット] を引き起こす SQL 文があります。 +> (詳細は、[MySQL](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) および +> [MariaDB](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/) の +> ドキュメントを参照して下さい。) マイグレーションを適用する diff --git a/docs/guide/ja/extension.integration.txt b/docs/guide/ja/extension.integration.txt index acde89ef41..58a300ac17 100644 --- a/docs/guide/ja/extension.integration.txt +++ b/docs/guide/ja/extension.integration.txt @@ -50,6 +50,43 @@ Yii::setPathOfAlias('Imagine',Yii::getPathOfAlias('application.vendors.Imagine') 上記のコードにおいて、エイリアスの名前の定義は、ライブラリで使用される名前空間の最初の部分と一致させなければなりません。 +サードパーティのオートローダを使う +---------------------------------- + +サードパーティのライブラリの中には (例えば PHPUnit のように) 自分自身のクラスオートローダを使うものがあります。 +それらは、Yii のオートローダとは異なる規則によって、クラスファイルのインクルードを実行します。 +Yii は PHP のインクルードパスをクラスファイルの「最後のソース」として使用するため、このような +サードパーティのオートローダを登録すると PHP のワーニングが生ずることがあります。 +~~~ +include(PHPUnit_Framework_TestCase.php) [function.include]: failed to open stream: No such file or directory +~~~ +このような問題を避けるためには、すべてのサードパーティのクラスオートローダを Yii のオートローダより前に登録して下さい。 +~~~ +[php] +require_once('PHPUnit/Autoload.php'); // サードパーティのオートローダを登録 +require_once('/path/to/framework/yii.php'); // Yii のオートローダを登録 +... +~~~ +サードパーティのクラスオートローダが独立した関数やメソッドで提供されている場合は、 +`Yii::registerAutoloader()` メソッドを使ってそれを登録しても構いません。 +この場合、Yii は自動的に自分自身のオートローダの前にサードパーティのオートローダを登録します。 +~~~ +[php] +require_once('/path/to/framework/yii.php'); // Yii のオートローダを登録 +... +Yii::registerAutoloader(array('SomeLibrary','autoload')); // サードパーティのオートローダを登録 +... +~~~ +さらに、PHP インクルードパスの使用を無効にすることで、サードパーティのオートローダに関する問題を避けることも出来ます。 +そのためには、アプリケーションを開始する前に、`YiiBase::$enableIncludePath` を `false` に設定します。 +~~~ +[php] +require_once('/path/to/framework/yii.php'); +$configFile='/path/to/config/main.php'; +Yii::$enableIncludePath = false; // PHP インクルードパスの使用を無効にする +Yii::createWebApplication($configFile)->run(); +~~~ + Yii をサードパーティのシステムで使う ------------------------------ diff --git a/docs/guide/ja/topics.auth.txt b/docs/guide/ja/topics.auth.txt index 6a3177e98a..0ac3b9009e 100644 --- a/docs/guide/ja/topics.auth.txt +++ b/docs/guide/ja/topics.auth.txt @@ -44,9 +44,9 @@ identity クラス定義の主な作業は [IUserIdentity::authenticate] メソ ユーザがログインフォームにユーザ名とパスワードを入力すると、その認証情報を検証するために、[アクティブレコード](/doc/guide/database.ar) を使って、データベースにある user テーブルに問い合わせをする、というものです。 実際には、この単純な例の中で、いくつかの事柄が示されています。 -1. データベースを使って認証情報を検証するように `authenticate()` を実装している。 -2. デフォルトの実装ではユーザ名を ID として返すので、`CUserIdentity::getId()` メソッドをオーバーライドして、`_id` プロパティを返している。 -3. 後に続くリクエストにおいて簡単に引き出せるように、`setState()` ([CBaseUserIdentity::setState]) メソッドを使ってその他の情報を保存している。 +1. データベースを使って認証情報を検証するように `authenticate()` を実装しています。 +2. デフォルトの実装ではユーザ名を ID として返すので、`CUserIdentity::getId()` メソッドをオーバーライドして、`_id` プロパティを返しています。 +3. 後に続くリクエストにおいて簡単に引き出せるように、`setState()` ([CBaseUserIdentity::setState]) メソッドを使ってその他の情報を保存しています。 ~~~ [php] @@ -58,7 +58,7 @@ class UserIdentity extends CUserIdentity $record=User::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID; - else if($record->password!==crypt($this->password,$record->password)) + else if(!CPasswordHelper::verifyPassword($this->password,$record->password)) $this->errorCode=self::ERROR_PASSWORD_INVALID; else { @@ -89,15 +89,12 @@ class UserIdentity extends CUserIdentity ユーザのパスワードをデータベースに安全に保存するためには、いくらか配慮が必要です。 ユーザのテーブル(またはそのバックアップ)を盗んだ攻撃者は、対策を施していない場合には、標準的な手法を使ってユーザのパスワードを回復することが出来ます。 +上記のサンプルコードは Yii 内蔵の [CPasswordHelper] (バージョン 1.1.14 以降で使用出来ます) を使って、パスワードのハッシュと検証をしています。 +[CPasswordHelper::hashPassword] は非常にクラックしにくいハッシュを返すものです。 + 具体的に言えば、パスワードをハッシュする前にソルトすべきであり、また、攻撃者が計算に長い時間を要するようなハッシュ関数を使うべきです。 上記のコード例では、PHP 組み込みの `crypt()` 関数を使っていますが、これは、正しく用いるなら、非常にクラックしにくいハッシュを生成するものです。 -これらの話題についての更に詳しい情報は、下記を参照して下さい。 - -- [PHP `crypt()` 関数](http://php.net/manual/ja/function.crypt.php) -- Yii Wiki 記事 [Use crypt() for password storage](http://www.yiiframework.com/wiki/425) - - ログインとログアウト ---------------- diff --git a/docs/guide/ja/topics.i18n.txt b/docs/guide/ja/topics.i18n.txt index c5129e071e..2f80e3c9d2 100644 --- a/docs/guide/ja/topics.i18n.txt +++ b/docs/guide/ja/topics.i18n.txt @@ -157,9 +157,9 @@ choice format を使用するには、翻訳メッセージが下記のように ~~~ [php] -Yii::t('app', 'n==1#one book|n>1#many books', array(1))); +Yii::t('app', 'n==1#one book|n>1#many books', array(1)); // または、1.1.6 以降なら -Yii::t('app', 'n==1#one book|n>1#many books', 1)); +Yii::t('app', 'n==1#one book|n>1#many books', 1); ~~~ 略記法として、式が数値のみの場合、`n==数値` と解釈されます。 diff --git a/docs/guide/quickstart.what-is-yii.txt b/docs/guide/quickstart.what-is-yii.txt index 1162f85792..d7d1e867fe 100644 --- a/docs/guide/quickstart.what-is-yii.txt +++ b/docs/guide/quickstart.what-is-yii.txt @@ -36,7 +36,7 @@ How does Yii Compare with Other Frameworks? Like most PHP frameworks, Yii is an MVC framework. -Yii excels other PHP frameworks at being efficient, feature-rich and +Yii excels among PHP frameworks at being efficient, feature-rich and clearly-documented. Yii is carefully designed from the ground up to be fit for serious Web application development. It is neither a byproduct of some project nor a conglomerate of third-party work. It is the result of the diff --git a/docs/guide/ru/basics.application.txt b/docs/guide/ru/basics.application.txt index ae68df8acb..c4c7c0a1d0 100644 --- a/docs/guide/ru/basics.application.txt +++ b/docs/guide/ru/basics.application.txt @@ -89,8 +89,9 @@ deny from all Компоненты приложения --------------------- + Функциональность объекта приложения может быть легко модифицирована и расширена благодаря компонентной архитектуре. -Приложение управляет набором компонентов, каждый из которых имеет специфические возможности. +Приложение управляет набором компонентов, каждый из которых реализует набор определённых возможностей. Например, приложение производит предварительную обработку запроса пользователя, используя компоненты [CUrlManager] и [CHttpRequest]. Изменяя значение свойства [components|CApplication::components], можно настроить классы и [значения свойств](/doc/guide/basics.component) @@ -188,7 +189,7 @@ Yii предопределяет набор компонентов ядра, к 0. Предварительная инициализация приложения через [CApplication::preinit()]. - 1. Инициализация автозагрузчика классов и обработчика ошибок. + 1. Инициализация обработчика ошибок. 2. Регистрация компонентов ядра. diff --git a/docs/guide/ru/basics.workflow.txt b/docs/guide/ru/basics.workflow.txt index a2e741287a..05f8ce3aec 100644 --- a/docs/guide/ru/basics.workflow.txt +++ b/docs/guide/ru/basics.workflow.txt @@ -34,4 +34,4 @@ 10. [Настройка производительности](/doc/guide/topics.performance) и развёртывание. -Для каждого из представленых этапов может потребоваться создание и применение тестов. \ No newline at end of file +Для каждого из представленных этапов может потребоваться создание и применение тестов. \ No newline at end of file diff --git a/docs/guide/ru/caching.data.txt b/docs/guide/ru/caching.data.txt index 0e3d571bcf..3b963493c1 100644 --- a/docs/guide/ru/caching.data.txt +++ b/docs/guide/ru/caching.data.txt @@ -18,8 +18,8 @@ Yii::app()->cache->set($id, $value); согласно некоторой политике кэширования (например, если места для хранения кэшированых данных не осталось, тогда самые старые данные удаляются). Чтобы изменить это поведение, мы можем установить срок действия кэша при вызове -метода [set()|CCache::set]. В этом случае данные будут удалены из кэша по истечении -определённого периода времени: +метода [set()|CCache::set]. В этом случае данные будут удалены из кэша по истечении как максимум +заданного периода времени: ~~~ [php] diff --git a/docs/guide/ru/database.ar.txt b/docs/guide/ru/database.ar.txt index 1a190fac30..84dd3b50c2 100644 --- a/docs/guide/ru/database.ar.txt +++ b/docs/guide/ru/database.ar.txt @@ -70,6 +70,7 @@ return array( В настоящий момент AR поддерживает следующие СУБД: - [MySQL 4.1 и выше](http://www.mysql.com) + - [MariaDB](https://mariadb.com) - [PostgreSQL 7.3 и выше](http://www.postgres.com) - [SQLite 2 и 3](http://www.sqlite.org) - [Microsoft SQL Server 2000 и выше](http://www.microsoft.com/sqlserver/) diff --git a/docs/guide/ru/database.dao.txt b/docs/guide/ru/database.dao.txt index dbba9a706c..23f5373603 100644 --- a/docs/guide/ru/database.dao.txt +++ b/docs/guide/ru/database.dao.txt @@ -43,7 +43,7 @@ $connection->active=false; // close connection Ниже представлены несколько основных форматов DSN: - SQLite: `sqlite:/path/to/dbfile` - - MySQL: `mysql:host=localhost;dbname=testdb` + - MySQL/MariaDB: `mysql:host=localhost;dbname=testdb` - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb` - SQL Server: `mssql:host=localhost;dbname=testdb` - Oracle: `oci:dbname=//localhost:1521/testdb` diff --git a/docs/guide/ru/database.migration.txt b/docs/guide/ru/database.migration.txt index b7348f1057..f454655ab3 100644 --- a/docs/guide/ru/database.migration.txt +++ b/docs/guide/ru/database.migration.txt @@ -204,8 +204,9 @@ Yii при применении миграции начнёт транзакци > Note|Примечание: Не все СУБД полностью поддерживают транзакции и не для всех > выражений. В том случае, если поддержки транзакций нет, реализовывать надо -> `up()` и `down()`. В случае использования MySQL некоторые выражения SQL могут вызвать -> [неявное применение транзакции](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html). +> `up()` и `down()`. В случае использования MySQL или MariaDB некоторые выражения SQL могут вызвать +> неявное применение транзакции. Смотрите документацию [MySQL](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) +> и [MariaDB](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/). Применение миграций diff --git a/docs/guide/ru/database.overview.txt b/docs/guide/ru/database.overview.txt index d9d340a121..1e7648e391 100644 --- a/docs/guide/ru/database.overview.txt +++ b/docs/guide/ru/database.overview.txt @@ -19,7 +19,7 @@ Yii Active Record (AR) реализует переработанный экземпляры класса, Yii AR избавляет от необходимости написания SQL-выражений, связанных с операциями CRUD (создание, чтение, обновление и удаление). -Несмотря на то что встроенные в Yii возможности для работы с БД подходят +Несмотря на то, что встроенные в Yii возможности для работы с БД подходят практически для всех задач, касающихся работы с БД, также возможно использование и других библиотек для работы с базами данных. Yii изначально был спроектирован таким образом, чтобы разработчик имел возможность работы со сторонними библиотеками. \ No newline at end of file diff --git a/docs/guide/ru/database.query-builder.txt b/docs/guide/ru/database.query-builder.txt index 0bc1f050c6..fc71e4173c 100644 --- a/docs/guide/ru/database.query-builder.txt +++ b/docs/guide/ru/database.query-builder.txt @@ -626,7 +626,7 @@ $command->delete('tbl_user', 'id=:id', array(':id'=>1)); * `text`: текстовый тип (длинная строка). Для MySQL конвертируется в `text`; * `integer`: целое. Для MySQL конвертируется в `int(11)`; * `float`: число с плавающей точкой. Для MySQL конвертируется в `float`; -* `decimal`: дясятичное число. Для MySQL конвертируется в `decimal`; +* `decimal`: десятичное число. Для MySQL конвертируется в `decimal`; * `datetime`: дата и время. Для MySQL конвертируется в `datetime`; * `timestamp`: метка времени. Для MySQL конвертируется в `timestamp`; * `time`: время. Для MySQL конвертируется в `time`; diff --git a/docs/guide/ru/extension.integration.txt b/docs/guide/ru/extension.integration.txt index 88ada83a6d..cc19d39e82 100644 --- a/docs/guide/ru/extension.integration.txt +++ b/docs/guide/ru/extension.integration.txt @@ -61,6 +61,49 @@ Yii::setPathOfAlias('Imagine',Yii::getPathOfAlias('application.vendors.Imagine') В приведённом выше коде имя заданного нами псевдонима должно соответствовать первой части пространства имён, которое используется в библиотеке. +Использование стронних загрузчиков классов +------------------------------------------ + +Некоторые сторонние библиотеки, такие как PHPUnit, используют собственные загрузчики классов, не совместимые с Yii. +Так как загрузчик классов Yii, если класс не удаётся найти, пробует загрузку из include path PHP, то регистрация +сторонних загрузчиков может дать PHP Warning: + +~~~ +include(PHPUnit_Framework_TestCase.php) [function.include]: failed to open stream: No such file or directory +~~~ + +Для избежания данной проблемы стоит регистрировать сторонние загрузчики до загрузчика Yii: + +~~~ +[php] +require_once('PHPUnit/Autoload.php'); // register 3rd-party autoloader +require_once('/path/to/framework/yii.php'); // register Yii autoloader +... +~~~ + +Если сторонний загрузчик реализован в виде отдельной функции или метода, можно использовать `Yii::registerAutoloader()` +для его регистрации. В этом случае он будет зарегистриован до загрузчика Yii автоматически. + +~~~ +[php] +require_once('/path/to/framework/yii.php'); // регистрируем загрузчик Yii +... +Yii::registerAutoloader(array('SomeLibrary','autoload')); // регистрируем сторонний загрузчик +... +~~~ + +Ещё один способ избежать проблем со сторонними загрузчиками — запретить PHP include path выставив +`YiiBase::$enableIncludePath` в `false` до запуска приложения: + +~~~ +[php] +require_once('/path/to/framework/yii.php'); +$configFile='/path/to/config/main.php'; +Yii::$enableIncludePath = false; // запрещаем PHP include path +Yii::createWebApplication($configFile)->run(); +~~~ + + Использование Yii в сторонних системах -------------------------------------- diff --git a/docs/guide/ru/test.fixture.txt b/docs/guide/ru/test.fixture.txt index b3ada04f0b..566cf9fab4 100644 --- a/docs/guide/ru/test.fixture.txt +++ b/docs/guide/ru/test.fixture.txt @@ -7,7 +7,7 @@ функции создания записи в приложении блога, каждый раз, когда мы выполняем тесты, таблицы, хранящие соответствующие данные о записях (например, таблицы `Post`, `Comment`), должны быть восстановлены к некоторому фиксированому -состоянию. [Документация по PHPUnit](http://www.phpunit.de/manual/current/en/fixtures.html) +состоянию. [Документация по PHPUnit](http://phpunit.de/manual/3.8/en/fixtures.html) хорошо описывает основную установку фикстур. В основном в этом разделе мы описываем установку фикстур базы данных так, как мы только что описали в примере. diff --git a/docs/guide/ru/topics.auth.txt b/docs/guide/ru/topics.auth.txt index 1153d7dbfa..b7bbb699a1 100644 --- a/docs/guide/ru/topics.auth.txt +++ b/docs/guide/ru/topics.auth.txt @@ -68,7 +68,7 @@ class UserIdentity extends CUserIdentity $record=User::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID; - else if($record->password!==crypt($this->password,$record->password)) + else if(!CPasswordHelper::verifyPassword($this->password,$record->password)) $this->errorCode=self::ERROR_PASSWORD_INVALID; else { @@ -103,12 +103,10 @@ class UserIdentity extends CUserIdentity ### Хранение паролей в базе данных -Безопасное хранение паролей в базе данных требует дополнительных мер предосторожности. Злоумышленник завладевший таблицей пользователей (или её резервной копией) может восстановить пароли при помощи стандартных методов если вы от них не защищаетесь. В частности, вы должны «солить» пароль перед его хэшированием и использовать стойкую хэш-функцию, результат работы которой мог бы занять у злоумышленника продолжительное время на перебор. Приведенный выше пример использует встроенную PHP-функцию `crypt()`, которая при правильном использовании возвращает достаточно устойчивый хэш. - -Дополнительные материалы по данной теме: - -- Стандартная [PHP-функция `crypt()`](http://php.net/manual/ru/function.crypt.php) -- Вики-статья [Use crypt() for password storage](http://www.yiiframework.com/wiki/425) +Безопасное хранение паролей в базе данных требует определённой аккуратности. Атакующий, получивший досутп к базе или +резеврным копиям может восстановить пароли используя достаточно распространённые приёмы, если от них не защититься. +Пример кода выше использует встроенный класс [CPasswordHelper], доступный с версии 1.1.14, для хеширования и проверки +пароля. [CPasswordHelper::hashPassword] возвращает стойкий ко взлому хеш. Вход и выход diff --git a/docs/guide/ru/topics.i18n.txt b/docs/guide/ru/topics.i18n.txt index 475d20bd12..1f392523d5 100644 --- a/docs/guide/ru/topics.i18n.txt +++ b/docs/guide/ru/topics.i18n.txt @@ -172,9 +172,9 @@ Yii поддерживает [формат выбора|CChoiceFormat], изве ~~~ [php] -Yii::t('app', 'n==1#one book|n>1#many books', array(1))); +Yii::t('app', 'n==1#one book|n>1#many books', array(1)); //or since 1.1.6 -Yii::t('app', 'n==1#one book|n>1#many books', 1)); +Yii::t('app', 'n==1#one book|n>1#many books', 1); ~~~ Если проверяется соответствие определённому числу, можно использовать сокращённую запись, diff --git a/docs/guide/test.fixture.txt b/docs/guide/test.fixture.txt index 9424846f7f..2e2daa3c68 100644 --- a/docs/guide/test.fixture.txt +++ b/docs/guide/test.fixture.txt @@ -1,7 +1,7 @@ Defining Fixtures ================= -Automated tests need to be executed many times. To ensure the testing process is repeatable, we would like to run the tests in some known state called *fixture*. For example, to test the post creation feature in a blog application, each time when we run the tests, the tables storing relevant data about posts (e.g. the `Post` table, the `Comment` table) should be restored to some fixed state. The [PHPUnit documentation](http://www.phpunit.de/manual/current/en/fixtures.html) has described well about generic fixture setup. In this section, we mainly describe how to set up database fixtures, as we just described in the example. +Automated tests need to be executed many times. To ensure the testing process is repeatable, we would like to run the tests in some known state called *fixture*. For example, to test the post creation feature in a blog application, each time when we run the tests, the tables storing relevant data about posts (e.g. the `Post` table, the `Comment` table) should be restored to some fixed state. The [PHPUnit documentation](http://phpunit.de/manual/3.8/en/fixtures.html) has described well about generic fixture setup. In this section, we mainly describe how to set up database fixtures, as we just described in the example. Setting up database fixtures is perhaps one of the most time-consuming parts in testing database-backed Web applications. Yii introduces the [CDbFixtureManager] application component to alleviate this problem. It basically does the following things when running a set of tests: diff --git a/docs/guide/topics.auth.txt b/docs/guide/topics.auth.txt index 7f169b28f4..61f16dc7d5 100644 --- a/docs/guide/topics.auth.txt +++ b/docs/guide/topics.auth.txt @@ -57,7 +57,7 @@ class UserIdentity extends CUserIdentity $record=User::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID; - else if($record->password!==crypt($this->password,$record->password)) + else if(!CPasswordHelper::verifyPassword($this->password,$record->password)) $this->errorCode=self::ERROR_PASSWORD_INVALID; else { @@ -86,13 +86,7 @@ also be saved in cookie. Make sure you do not declare sensitive information ### Storing passwords in the database -Secure storage of user passwords in a database requires some care. An attacker that has stolen your user table (or a backup of it) can recover passwords using standard techniques if you don't protect against them. In particular you should salt the password before hashing and use a hash function that takes the attacker a long time to compute. The above code example uses the built-in PHP `crypt()` function which, with appropriate use, returns hashes that are very hard to crack. - -To learn more about these topics please read. - -- [PHP `crypt()` function](http://php.net/manual/en/function.crypt.php) -- Yii Wiki article [Use crypt() for password storage](http://www.yiiframework.com/wiki/425) - +Secure storage of user passwords in a database requires some care. An attacker that has stolen your user table (or a backup of it) can recover passwords using standard techniques if you don't protect against them. The above code example uses Yii built-in [CPasswordHelper] to hash the password and to validate it (since version 1.1.14). [CPasswordHelper::hashPassword] returns hashes that are very hard to crack. Login and Logout ---------------- diff --git a/docs/guide/topics.i18n.txt b/docs/guide/topics.i18n.txt index e4ba67232e..3b32d300cb 100644 --- a/docs/guide/topics.i18n.txt +++ b/docs/guide/topics.i18n.txt @@ -205,9 +205,9 @@ translated message: ~~~ [php] -Yii::t('app', 'n==1#one book|n>1#many books', array(1))); +Yii::t('app', 'n==1#one book|n>1#many books', array(1)); //or since 1.1.6 -Yii::t('app', 'n==1#one book|n>1#many books', 1)); +Yii::t('app', 'n==1#one book|n>1#many books', 1); ~~~ As a shortcut notation, if an expression is a number, it will be treated as diff --git a/docs/guide/uk/basics.application.txt b/docs/guide/uk/basics.application.txt index 3e18215ab6..ab223248b5 100644 --- a/docs/guide/uk/basics.application.txt +++ b/docs/guide/uk/basics.application.txt @@ -188,7 +188,7 @@ Yii визначає набір компонентів ядра, що надаю 0. Попередня ініціалізація додатку через [CApplication::preinit()]. - 1. Ініціалізація автозавантажувача класів та обробника помилок. + 1. Ініціалізація обробника помилок. 2. Реєстрація компонентів ядра. diff --git a/docs/guide/uk/basics.convention.txt b/docs/guide/uk/basics.convention.txt index 98d258877c..4e5745fee4 100644 --- a/docs/guide/uk/basics.convention.txt +++ b/docs/guide/uk/basics.convention.txt @@ -77,7 +77,7 @@ Yii рекомендує іменувати змінні, функції та к Файл відображення — це PHP-скрипт, що містить HTML і PHP-код, в основному для задання відображення користувальницького інтерфейсу. -Конфігураційні файли можуть іменуватися довільним образом. Файл конфігурації — +Конфігураційні файли можуть іменуватися довільним чином. Файл конфігурації — це PHP-скрипт, чиє єдине призначення — повертати асоціативний масив, що представляє конфігурацію. Директорія diff --git a/docs/guide/uk/caching.overview.txt b/docs/guide/uk/caching.overview.txt index 804642497a..eddc76bb24 100644 --- a/docs/guide/uk/caching.overview.txt +++ b/docs/guide/uk/caching.overview.txt @@ -2,7 +2,7 @@ ========= Кешування — простий та ефективний спосіб покращення продуктивності веб-додатка. -Зберігаючи відносно статичні дані в кеші і використовуючи ці дані з кешу коли буде потрібно, +Зберігаючи відносно статичні дані в кеші і використовуючи ці дані з кешу, коли буде потрібно, ми економимо час генерації даних. Використання кешу в Yii — це, головним чином, конфігурування та виклик компонента кешу. Нижче показана конфігурація, @@ -55,7 +55,7 @@ Yii забезпечує різні кеш-компоненти, кешуючи - [CFileCache]: для зберігання кешованих даних використовуються файли. Добре підходить для великих одиниць даних, таких як цілі сторінки; - - [CDummyCache]: кеш-пустишка. Нічого не кешує. Потрібен для спрощення коду, + - [CDummyCache]: кеш-пустушка. Нічого не кешує. Потрібен для спрощення коду, необхідного при перевірці доступності кеша. Ми можемо скористатися даними компонентом під час розробки або у випадку, якщо сервер не підтримує кешування. Коли здійснення кешування буде можливо, ми зможемо його застосувати. В обох випадках буде використаний ідентичний код diff --git a/docs/guide/uk/database.ar.txt b/docs/guide/uk/database.ar.txt index cf8ada6f1f..69c8c27b68 100644 --- a/docs/guide/uk/database.ar.txt +++ b/docs/guide/uk/database.ar.txt @@ -52,6 +52,7 @@ return array( На даний момент AR підтримує наступні СУБД: - [MySQL 4.1 та вище](http://www.mysql.com) + - [MariaDB](https://mariadb.com) - [PostgreSQL 7.3 та вище](http://www.postgres.com) - [SQLite 2 та 3](http://www.sqlite.org) - [Microsoft SQL Server 2000 та вище](http://www.microsoft.com/sqlserver/) diff --git a/docs/guide/uk/database.arr.txt b/docs/guide/uk/database.arr.txt index 258cdd411d..42f831cf55 100644 --- a/docs/guide/uk/database.arr.txt +++ b/docs/guide/uk/database.arr.txt @@ -45,7 +45,7 @@ SQLite 3.6.19 та більш ранні версії не підтримуют Наприклад, `Post` належить багатьом `Category`, а у `Category` є багато `Post`. Існує пʼятий спеціальний тип, який виконує агреговані запити на звʼязаних записах - він називається `STAT`. -Зверніться будь-ласка до розділу [Статистичний запит](/doc/guide/database.arr#statistical-query) за деталями. +Зверніться, будь ласка, до розділу [Статистичний запит](/doc/guide/database.arr#statistical-query) за деталями. Оголошуючи відношення в AR, ми перевизначаємо метод [relations()|CActiveRecord::relations] класу [CActiveRecord]. Цей метод повертає масив з конфігурацією відношень. Кожен елемент масиву представляє один звʼязок у наступному форматі: diff --git a/docs/guide/uk/database.dao.txt b/docs/guide/uk/database.dao.txt index 7983466b97..c108e10020 100644 --- a/docs/guide/uk/database.dao.txt +++ b/docs/guide/uk/database.dao.txt @@ -35,7 +35,7 @@ $connection->active=false; // close connection Нижче представлені кілька основних форматів DSN: - SQLite: `sqlite:/path/to/dbfile` - - MySQL: `mysql:host=localhost;dbname=testdb` + - MySQL/MariaDB: `mysql:host=localhost;dbname=testdb` - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb` - SQL Server: `mssql:host=localhost;dbname=testdb` - Oracle: `oci:dbname=//localhost:1521/testdb` diff --git a/docs/guide/uk/database.migration.txt b/docs/guide/uk/database.migration.txt index 1e478ed81f..cd6011de8a 100644 --- a/docs/guide/uk/database.migration.txt +++ b/docs/guide/uk/database.migration.txt @@ -3,7 +3,11 @@ > Note|Примітка: Міграції доступні з версії 1.1.6. -Як і початковий код, структура бази даних змінюється в процесі розробки та підтримки додатку. Приміром, під час розробки може знадобитися додати нову таблицю або вже після розміщення додатка на сервері додати індекс або стовпець. При цьому важливо відстежувати зміни в структурі бази даних (звані **міграціями**) також, як ми робимо це для нашого початкового коду. Якщо початковий код і база даних не відповідають один одному, швидше за все все додаток не буде працювати. Саме тому в Yii є підтримка міграцій, що дозволяє відстежувати зміни в базі даних, застосовувати міграції або відкочувати вже застосовані. +Як і початковий код, структура бази даних змінюється в процесі розробки та підтримки додатку. +Приміром, під час розробки може знадобитися додати нову таблицю або вже після розміщення додатка на сервері додати індекс або стовпець. +При цьому важливо відстежувати зміни в структурі бази даних (звані **міграціями**) також, як ми робимо це для нашого початкового коду. +Якщо початковий код і база даних не відповідають один одному, швидше за все все додаток не буде працювати. +Саме тому в Yii є підтримка міграцій, що дозволяє відстежувати зміни в базі даних, застосовувати міграції або відкочувати вже застосовані. Нижче наведено поетапний процес як ми можемо використовувати міграції бази даних при розробці: @@ -12,9 +16,12 @@ 3. Павло оновлюється із системи контролю версій і отримує нову міграцію. 4. Павло застосовує міграцію до своєї локальної бази даних. -У Yii управління міграціями проводиться через консольну команду `yiic migrate`, яка підтримує створення нових міграцій, застосування, відкат і повторне застосування міграцій, перегляд історії міграцій і нових міграцій. +У Yii управління міграціями проводиться через консольну команду `yiic migrate`, яка підтримує створення нових міграцій, +застосування, відкат і повторне застосування міграцій, перегляд історії міграцій і нових міграцій. -> Note|Примітка: При роботі з командою `migrate` рекомендується використовувати yiic програми (тобто після `cd path/to/protected`), а не yiic з директорії `framework`. Переконайтеся, що директорія `protected/migrations` існує і доступна для запису. Також перевірте налаштування зʼєднання з базою даних в `protected/config/console.php`. +> Note|Примітка: При роботі з командою `migrate` рекомендується використовувати yiic програми (тобто після `cd path/to/protected`), +а не yiic з директорії `framework`. Переконайтеся, що директорія `protected/migrations` існує і доступна для запису. +Також перевірте налаштування зʼєднання з базою даних в `protected/config/console.php`. Створення міграцій ------------------ @@ -25,7 +32,9 @@ yiic migrate create ~~~ -Обовʼязковий параметр `name` повинен містити дуже короткий опис міграції (таке, як, наприклад, `create_news_table`). Як буде показано далі, цей параметр використовується як частина імені класу міграції, тому використовувати можна тільки букви, цифри та знаки підкреслення. +Обовʼязковий параметр `name` повинен містити дуже короткий опис міграції (таке, як, наприклад, `create_news_table`). +Як буде показано далі, цей параметр використовується як частина імені класу міграції, +тому використовувати можна тільки букви, цифри та знаки підкреслення. ~~~ yiic migrate create create_news_table @@ -62,13 +71,17 @@ class m101129_185401_create_news_table extends CDbMigration } ~~~ -Варто відзначити, що імʼя класу збігається з іменем файлу і будується як `m_`, де `` — це час створення міграції в UTC (у форматі `yymmdd_hhmmss`), а `` — те, що передано в параметрі `name` команди. +Варто відзначити, що імʼя класу збігається з іменем файлу і будується як `m_`, +де `` — це час створення міграції в UTC (у форматі `yymmdd_hhmmss`), а `` — те, що передано в параметрі `name` команди. Метод `up()` повинен містити код, який виконує міграцію, а метод `down()` може містити код, який скасовує зроблене в `up()`. -Іноді реалізувати `down()` не виходить. Приміром, якщо в `up()` із таблиці видаляються дані, в `down()` повернути їх не вийде. У цьому випадку міграція називається незворотною, що означає неможливість повернення до попереднього стану бази даних. У наведеному вище коді метод `down()` повертає `false`. Це означає неможливість відкату міграції. +Іноді реалізувати `down()` не виходить. Приміром, якщо в `up()` із таблиці видаляються дані, в `down()` повернути їх не вийде. +У цьому випадку міграція називається незворотною, що означає неможливість повернення до попереднього стану бази даних. +У наведеному вище коді метод `down()` повертає `false`. Це означає неможливість відкату міграції. -> Info|Інформація: Починаючи з версії 1.1.7, якщо метод `up()` або метод `down()` повертають `false`, всі наступні міграції не будуть застосовані. В 1.1.6 для цього було необхідно викинути виняток. +> Info|Інформація: Починаючи з версії 1.1.7, якщо метод `up()` або метод `down()` повертають `false`, +всі наступні міграції не будуть застосовані. В 1.1.6 для цього було необхідно викинути виняток. Розглянемо як приклад міграцію, яка створює таблицю з новинами. @@ -92,16 +105,22 @@ class m101129_185401_create_news_table extends CDbMigration } ~~~ -Базовий клас [CDbMigration] надає набір методів для роботи з даними і структурою бази даних. Приміром, за допомогою [CDbMigration::createTable] можна створити нову таблицю, а [CDbMigration::insert] додасть рядок з даними. Всі ці методи використовують підключення до бази даних, що повертає [CDbMigration::getDbConnection()], що за замовчуванням еквівалентно `Yii::app()->db`. +Базовий клас [CDbMigration] надає набір методів для роботи з даними і структурою бази даних. +Приміром, за допомогою [CDbMigration::createTable] можна створити нову таблицю, а [CDbMigration::insert] додасть рядок з даними. +Всі ці методи використовують підключення до бази даних, що повертає [CDbMigration::getDbConnection()], +що за замовчуванням еквівалентно `Yii::app()->db`. -> Info|Інформація: Як ви могли помітити, методи [CDbMigration] дуже схожі на методи [CDbCommand]. І це насправді так. Єдина відмінність у тому, що методи [CDbMigration] підраховують витрачений час на їх виконання і виводять повідомлення про параметри методів. +> Info|Інформація: Як ви могли помітити, методи [CDbMigration] дуже схожі на методи [CDbCommand]. +І це насправді так. Єдина відмінність у тому, що методи [CDbMigration] підраховують витрачений час +на їх виконання і виводять повідомлення про параметри методів. Транзакційні міграції --------------------- > Info|Інформація: Дана можливість підтримується починаючи з версії 1.1.7. -При застосуванні складних міграцій для дотримання цілісності та звʼязності потрібно або виконати всі запити, або, якщо запит не виконався, скасувати попередні запити. Для досягнення цієї мети можна використовувати транзакції. +При застосуванні складних міграцій для дотримання цілісності та звʼязності потрібно або виконати всі запити, або, +якщо запит не виконався, скасувати попередні запити. Для досягнення цієї мети можна використовувати транзакції. Можна почати транзакцію явно і заключити в неї весь код, який змінює базу даних: @@ -155,10 +174,14 @@ class m101129_185401_create_news_table extends CDbMigration } ~~~ -Yii при застосуванні міграції почне транзакцію і, потім, виконає код в `safeUp()` або `safeDown()`. Якщо при цьому виникне яка-небудь помилка, відбудеться відкат транзакції, тобто база повернеться у початковий стан. - -> Note|Примітка: Не всі СУБД повністю підтримують транзакції і не для всіх виразів. У тому випадку, якщо підтримки транзакцій немає, реалізовувати треба `up()` і `down()`. У разі MySQL, деякі вирази SQL можуть викликати [неявне застосування транзакції](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html). +Yii при застосуванні міграції почне транзакцію і, потім, виконає код в `safeUp()` або `safeDown()`. +Якщо при цьому виникне яка-небудь помилка, відбудеться відкат транзакції, тобто база повернеться у початковий стан. +> Note|Примітка: Не всі СУБД повністю підтримують транзакції і не для всіх виразів. У тому випадку, +якщо підтримки транзакцій немає, реалізовувати треба `up()` і `down()`. +У разі MySQL та MariaDB, деякі вирази SQL можуть викликати неявне застосування транзакції +(детальніше про це у документації до [MySQL](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) +та [MariaDB](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/)). Застосування міграцій --------------------- @@ -169,9 +192,12 @@ Yii при застосуванні міграції почне транзакц yiic migrate ~~~ -Команда відобразить перелік всіх нових міграцій і, у випадку позитивної відповіді, по черзі запустить метод `up()` у кожному класі міграції в порядку його створення. +Команда відобразить перелік всіх нових міграцій і, у випадку позитивної відповіді, +по черзі запустить метод `up()` у кожному класі міграції в порядку його створення. -Після застосування міграції у таблицю `tbl_migration` буде внесений відповідний запис. Це дозволяє дізнатися, які міграції вже застосовані, а які ні. Якщо таблиця `tbl_migration` не існує, вона буде створена автоматично в базі даних, зазначеної у компоненті `db` додатка. +Після застосування міграції у таблицю `tbl_migration` буде внесений відповідний запис. +Це дозволяє дізнатися, які міграції вже застосовані, а які ні. Якщо таблиця `tbl_migration` не існує, +вона буде створена автоматично в базі даних, зазначеної у компоненті `db` додатка. Іноді потрібно застосувати лише одну або декілька нових міграцій. Для цього можна використовувати наступну команду: @@ -187,7 +213,10 @@ yiic migrate up 3 yiic migrate to 101129_185401 ~~~ -У якості параметра, що вказує версію, до якої потрібно привести базу даних, використовується частина імені файлу, що відповідає часу створення міграції. Якщо між останньою застосованою і вказаною міграціями кілька міграцій, то всі вони будуть застосовані. Якщо зазначена міграція вже застосовувалася, то буде проведений відкат всіх міграцій, застосованих після неї (описано в наступному розділі). +У якості параметра, що вказує версію, до якої потрібно привести базу даних, використовується частина імені файлу, +що відповідає часу створення міграції. Якщо між останньою застосованою і вказаною міграціями кілька міграцій, +то всі вони будуть застосовані. Якщо зазначена міграція вже застосовувалася, то буде проведений відкат всіх міграцій, +застосованих після неї (описано в наступному розділі). Відкат міграцій --------------- @@ -198,20 +227,24 @@ yiic migrate to 101129_185401 yiic migrate down [step] ~~~ -де необовʼязковий параметр `step` задає кількість міграцій, які треба відкотити. За замовчуванням відкочується одна остання застосована міграція. +де необовʼязковий параметр `step` задає кількість міграцій, які треба відкотити. +За замовчуванням відкочується одна остання застосована міграція. -Як було описано раніше, не всі міграції можна відкотити. При спробі відкату таких міграцій буде викинуто виключення і процес відкату буде перерваний. +Як було описано раніше, не всі міграції можна відкотити. +При спробі відкату таких міграцій буде викинуто виключення і процес відкату буде перерваний. Повторне застосування міграцій ------------------------------ -Повторне застосування міграції проводиться шляхом послідовного відкату і застосування. Здійснити це можна наступною командою: +Повторне застосування міграції проводиться шляхом послідовного відкату і застосування. +Здійснити це можна наступною командою: ~~~ yiic migrate redo [step] ~~~ -де необовʼязковий параметр `step` вказує кількість міграцій, які необхідно застосувати ще раз. За замовчуванням повторюється одна остання міграція. +де необовʼязковий параметр `step` вказує кількість міграцій, які необхідно застосувати ще раз. +За замовчуванням повторюється одна остання міграція. Перегляд інформації про міграції -------------------------------- @@ -230,13 +263,15 @@ yiic migrate new [limit] Зміна історії міграцій ---------------------- -Іноді потрібно змінити історію міграцій так, щоб поточна версія була замінена на вказану без застосування або відкату міграцій. Часто це потрібно при створенні нової міграції. Для цього можна використовувати наступну команду: +Іноді потрібно змінити історію міграцій так, щоб поточна версія була замінена на вказану без застосування або відкату міграцій. +Часто це потрібно при створенні нової міграції. Для цього можна використовувати наступну команду: ~~~ yiic migrate mark 101129_185401 ~~~ -Ця команда дуже схожа на `yiic migrate to`, але вона лише змінює таблицю історії міграцій до зазначеної версії без застосування або відкату самих міграцій. +Ця команда дуже схожа на `yiic migrate to`, але вона лише змінює таблицю історії міграцій до зазначеної +версії без застосування або відкату самих міграцій. Налаштування команди міграцій ----------------------------- @@ -247,15 +282,22 @@ yiic migrate mark 101129_185401 Команда міграцій може бути налаштована чотирма опціями: -* `interactive`: чи використовувати інтерактивний режим. За замовчуванням true, тобто при застосуванні міграції буде виводитися підтвердження. Якщо параметр виставлений у false, то міграції можна застосувати у фоновому режимі; +* `interactive`: чи використовувати інтерактивний режим. За замовчуванням true, тобто при застосуванні міграції буде виводитися підтвердження. +Якщо параметр виставлений у false, то міграції можна застосувати у фоновому режимі; -* `migrationPath`: вказує директорію, в якій зберігаються всі файли міграцій. Шлях повинен вказуватися у форматі псевдоніма і відповідна йому директорія повинна існувати. Якщо параметр не вказаний, буде використана піддиректорії `migrations`, що знаходиться всередині директорії з додатком; +* `migrationPath`: вказує директорію, в якій зберігаються всі файли міграцій. Шлях повинен вказуватися у форматі псевдоніма +і відповідна йому директорія повинна існувати. Якщо параметр не вказаний, +буде використана піддиректорії `migrations`, що знаходиться всередині директорії з додатком; -* `migrationTable`: вказує імʼя таблиці в базі даних, яка зберігає історію міграцій. За замовчуванням воно дорівнює `tbl_migration`. Структура таблиці наступна: `version varchar(255) primary key, apply_time integer`; +* `migrationTable`: вказує імʼя таблиці в базі даних, яка зберігає історію міграцій. За замовчуванням воно дорівнює `tbl_migration`. +Структура таблиці наступна: `version varchar(255) primary key, apply_time integer`; * `connectionID`: вказує ідентифікатор компонента бази даних. За умовчанням це 'db'; -* `templateFile`: вказує шлях до файлу, який використовується як шаблон для генерації класів міграцій. Шлях повинен вказуватися як псевдонім (тобто як `application.migrations.template`). Якщо шлях не заданий, буде використовуватися внутрішній шаблон. У шаблоні токен `{ClassName}` буде замінений імʼям класу міграції. +* `templateFile`: вказує шлях до файлу, який використовується як шаблон для генерації класів міграцій. +Шлях повинен вказуватися як псевдонім (тобто як `application.migrations.template`). +Якщо шлях не заданий, буде використовуватися внутрішній шаблон. +У шаблоні токен `{ClassName}` буде замінений імʼям класу міграції. Для зазначення опцій використовується наступний формат: @@ -263,7 +305,8 @@ yiic migrate mark 101129_185401 yiic migrate up --option1=value1 --option2=value2 ... ~~~ -Наприклад, якщо необхідно мігрувати модуль `forum`, файли міграцій якого розташовані у директорії модуля `migrations`, можна скористатися наступною командою: +Наприклад, якщо необхідно мігрувати модуль `forum`, файли міграцій якого розташовані у директорії модуля `migrations`, +можна скористатися наступною командою: ~~~ yiic migrate up --migrationPath=ext.forum.migrations @@ -278,7 +321,10 @@ yiic migrate --interactive=0 ### Глобальна конфігурація команди -У той час, як опції командного рядка дозволяють нам на льоту конфігурувати команду міграцій, іноді потрібно застосувати налаштування раз і назавжди. Приміром, нам може знадобитися використовувати іншу таблицю для зберігання історії міграцій або використовувати свій шаблон міграції. Це можна зробити, змінивши налаштування консольного додатку наступним чином: +У той час, як опції командного рядка дозволяють нам на льоту конфігурувати команду міграцій, +іноді потрібно застосувати налаштування раз і назавжди. +Приміром, нам може знадобитися використовувати іншу таблицю для зберігання історії міграцій або використовувати свій шаблон міграції. +Це можна зробити, змінивши налаштування консольного додатку наступним чином: ~~~ [php] @@ -298,4 +344,5 @@ return array( ); ~~~ -Тепер, при запуску команди `migrate`, зазначені вище налаштування будуть застосовані без введення яких-небудь додаткових параметрів. \ No newline at end of file +Тепер, при запуску команди `migrate`, зазначені вище налаштування будуть застосовані +без введення яких-небудь додаткових параметрів. \ No newline at end of file diff --git a/docs/guide/uk/extension.integration.txt b/docs/guide/uk/extension.integration.txt index f39487f0ff..a7a814760a 100644 --- a/docs/guide/uk/extension.integration.txt +++ b/docs/guide/uk/extension.integration.txt @@ -1,12 +1,23 @@ Використання сторонніх бібліотек ================================ -Yii спочатку спроектований таким чином, щоб використання сторонніх бібліотек з метою розширення функціоналу Yii, відбувалося легко і невимушено. Дуже часто при використанні в роботі сторонніх бібліотек, розробники стикаються з проблемами іменування класів і підключення файлів. Оскільки всі класи Yii мають префікс `C`, то ймовірність виникнення конфліктів імен істотно нижче. А завдяки тому, що для підключення файлів Yii використовує [автозавантаження SPL](http://php.net/manual/en/function.spl-autoload.php), робота з бібліотеками, що використовують для підключення файлів класів цей механізм автозавантаження або ж відносний шлях підключення в РНР (PHP include path), стає істотно приємніше. +Yii спочатку спроектований таким чином, щоб використання сторонніх бібліотек +з метою розширення функціоналу Yii, відбувалося легко і невимушено. +Дуже часто при використанні в роботі сторонніх бібліотек, розробники стикаються +з проблемами іменування класів і підключення файлів. Оскільки всі класи Yii мають префікс `C`, +то ймовірність виникнення конфліктів імен істотно нижче. +А завдяки тому, що для підключення файлів Yii використовує +[автозавантаження SPL](http://php.net/manual/en/function.spl-autoload.php), +робота з бібліотеками, що використовують для підключення файлів класів цей механізм +автозавантаження або ж відносний шлях підключення в РНР (PHP include path), стає істотно приємніше. Нижче наведено приклад, що ілюструє використання в Yii-додатку компонента -[Zend_Search_Lucene](http://www.zendframework.com/manual/en/zend.search.lucene.html) із [Zend framework](http://www.zendframework.com). +[Zend_Search_Lucene](http://www.zendframework.com/manual/en/zend.search.lucene.html) +із [Zend framework](http://www.zendframework.com). -Насамперед, розпаковуємо реліз з Zend framework в папку `protected/vendors`, де `protected` — це [базова директорія додатку](/doc/guide/basics.application#application-base-directory). Переконайтеся в тому, що файл `protected/vendors/Zend/Search/Lucene.php` існує. +Насамперед, розпаковуємо реліз з Zend framework в папку `protected/vendors`, +де `protected` — це [базова директорія додатку](/doc/guide/basics.application#application-base-directory). +Переконайтеся в тому, що файл `protected/vendors/Zend/Search/Lucene.php` існує. Далі, на самому початку класу контролера, додаємо рядок: @@ -16,7 +27,10 @@ Yii::import('application.vendors.*'); require_once('Zend/Search/Lucene.php'); ~~~ -Код, наведений вище, підключає файл класу `Lucene.php`. Оскільки використовується відносний шлях, то необхідно змінити відносний шлях підключення в РНР (PHP include path) таким чином, щоб додаток міг знайти файл. Робиться це шляхом виклику методу `Yii::import` перед `require_once`. +Код, наведений вище, підключає файл класу `Lucene.php`. +Оскільки використовується відносний шлях, то необхідно змінити відносний шлях підключення +в РНР (PHP include path) таким чином, щоб додаток міг знайти файл. Р +обиться це шляхом виклику методу `Yii::import` перед `require_once`. Після того, як зроблено все описане, можна використовувати клас `Lucene` в діях контролера наступним чином: @@ -29,9 +43,12 @@ $hits=$lucene->find(strtolower($keyword)); Підключення бібліотек, що використовують простори імен ------------------------------------------------------ -Для того, щоб підключити бібліотеку, що використовує простір імен відповідно до угоди [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) (наприклад, Zend Framework 2 або Symfony2), необхідно зареєструвати її корінь як псевдонім шляху. +Для того, щоб підключити бібліотеку, що використовує простір імен відповідно до угоди +[PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) +(наприклад, Zend Framework 2 або Symfony2), необхідно зареєструвати її корінь як псевдонім шляху. -У якості прикладу будемо використовувати [Imagine](https://github.com/avalanche123/Imagine). Якщо ми скопіюємо директорію `Imagine` в `protected/vendors`, то використовувати бібліотеку можна буде наступним чином: +У якості прикладу будемо використовувати [Imagine](https://github.com/avalanche123/Imagine). +Якщо ми скопіюємо директорію `Imagine` в `protected/vendors`, то використовувати бібліотеку можна буде наступним чином: ~~~ [php] @@ -42,12 +59,53 @@ Yii::setPathOfAlias('Imagine',Yii::getPathOfAlias('application.vendors.Imagine') // і т.д. ~~~ -У наведеному вище коді імʼя заданого нами псевдоніма має відповідати першій частині простору імен, яке використовується в бібліотеці. +У наведеному вище коді імʼя заданого нами псевдоніма має відповідати першій частині простору імен, +яке використовується в бібліотеці. + +Використання сторонніх автозавантажувачів +----------------------------------------- + +Деякі сторонні бібліотеки (наприклад PHPUnit) використовують свої власні автозавантажувачі класів, +які виконують підключення файлу класа за правилами, які відрізняються від тих, +що використовуються автозавантажувачем у Yii. Оскільки Yii використовує шлях підключення PHP як "останнє джерело" файлів класів, +реєстрація таких стороннії автозавантажувачів може призвести до Попередження PHP (Warning): +~~~ +include(PHPUnit_Framework_TestCase.php) [function.include]: failed to open stream: No such file or directory +~~~ +Для уникнення такої проблеми - переконайтеся, що будь-який сторонній автозавантажувач класів +зареєстрований до автозавантажувача Yii: +~~~ +[php] +require_once('PHPUnit/Autoload.php'); // реєструємо сторонній автозавантажувач +require_once('/path/to/framework/yii.php'); // реєструємо автозавантажувач Yii +... +~~~ +Якщо сторонній автозавантажувач классів йде як окрема функція або метод, +ви можете використовувати `Yii::registerAutoloader()` для його реєстрації. +У цьому випадку Yii зареєструє його перед власним автозавантажувачем автоматично. +~~~ +[php] +require_once('/path/to/framework/yii.php'); // реєструємо автозавантажувач Yii +... +Yii::registerAutoloader(array('SomeLibrary','autoload')); // реєструємо сторонній автозавантажувач +... +~~~ +Ви також можете уникнути проблем із сторонніми автозавантажувачами, +відключаючи використання шляху підключення PHP перед виконанням додатка за допомогою параметра +`YiiBase::$enableIncludePath` рівним `false`: +~~~ +[php] +require_once('/path/to/framework/yii.php'); +$configFile='/path/to/config/main.php'; +Yii::$enableIncludePath = false; // відключаємо використання шляху підключення PHP +Yii::createWebApplication($configFile)->run(); +~~~ Використання Yii у сторонніх системах ------------------------------------- -Yii може бути використаний як бібліотека для розробки і поліпшення сторонніх систем, таких як WordPress, Joomla та інших. Для того, щоб скористатися Yii необхідно включити наведений нижче код у сторонню систему: +Yii може бути використаний як бібліотека для розробки і поліпшення сторонніх систем, таких як WordPress, +Joomla та інших. Для того, щоб скористатися Yii необхідно включити наведений нижче код у сторонню систему: ~~~ [php] @@ -55,6 +113,9 @@ require_once('шлях/до/yii.php'); Yii::createWebApplication('шлях/до/config.php'); ~~~ -Даний код дуже схожий на той, який використовується в `index.php` звичайного додатку. Відмінність у тому, що після створення екземпляра додатку не викликається метод `run()`. +Даний код дуже схожий на той, який використовується в `index.php` звичайного додатку. +Відмінність у тому, що після створення екземпляра додатку не викликається метод `run()`. -Тепер при розробці сторонньої системи можна використовувати більшість можливостей Yii. Наприклад, для отримання доступу до екземпляра додатку можна використовувати `Yii::app()`. Також можна використовувати DAO, ActiveRecord моделі, валідацію і т.д. \ No newline at end of file +Тепер при розробці сторонньої системи можна використовувати більшість можливостей Yii. +Наприклад, для отримання доступу до екземпляра додатку можна використовувати `Yii::app()`. +Також можна використовувати DAO, ActiveRecord моделі, валідацію і т.д. \ No newline at end of file diff --git a/docs/guide/uk/form.model.txt b/docs/guide/uk/form.model.txt index 54e661acb4..2eafff5bb4 100644 --- a/docs/guide/uk/form.model.txt +++ b/docs/guide/uk/form.model.txt @@ -182,7 +182,7 @@ public function ValidatorName($attribute,$params) { … } array('username', 'required'), // довжина імені користувача повинна бути від 3 до 12 символів включно array('username', 'length', 'min'=>3, 'max'=>12), -// у сценарії регістрації значення полів «password» та «password2» повинні бути рівні +// у сценарії реєстрації значення полів «password» та «password2» повинні бути рівні array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), // у сценарії аутентифікації поле `password` повинно бути перевірене на відповідність вказаного імені користувача array('password', 'authenticate', 'on'=>'login'), @@ -242,7 +242,7 @@ $model=new User('login'); if(isset($_POST['User'])) $model->attributes=$_POST['User']; -// сценарій регістрації +// сценарій реєстрації $model=new User('register'); if(isset($_POST['User'])) $model->attributes=$_POST['User']; diff --git a/docs/guide/uk/quickstart.first-app-yiic.txt b/docs/guide/uk/quickstart.first-app-yiic.txt index 131578d643..db4f026774 100644 --- a/docs/guide/uk/quickstart.first-app-yiic.txt +++ b/docs/guide/uk/quickstart.first-app-yiic.txt @@ -2,7 +2,7 @@ ============================================== > Note|Примітка: Генератори коду `yiic shell` вважаються застарілими, починаючи з версії -> 1.1.2. Будь-ласка, використовуйте більш потужні, розширювані веб-генератори +> 1.1.2. Будь ласка, використовуйте більш потужні, розширювані веб-генератори > [Gii](/doc/guide/topics.gii). Відкриємо консоль і виконаємо наступні команди: diff --git a/docs/guide/uk/quickstart.first-app.txt b/docs/guide/uk/quickstart.first-app.txt index 2031e10b3f..6b25997dbc 100644 --- a/docs/guide/uk/quickstart.first-app.txt +++ b/docs/guide/uk/quickstart.first-app.txt @@ -131,7 +131,7 @@ return array( ); ~~~ -У наведеному вище коду вказано, що додаток повинен підключитися до бази даних SQLite +У наведеному вище коді вказано, що додаток повинен підключитися до бази даних SQLite `WebRoot/testdrive/protected/data/testdrive.db` як тільки це знадобиться. Відмітимо, що база даних SQLite вже вімкнена до згенерованого додатку. У цій базі є тільки одна таблиця `tbl_user`: diff --git a/docs/guide/uk/test.functional.txt b/docs/guide/uk/test.functional.txt index 842a799a56..05b73408c9 100644 --- a/docs/guide/uk/test.functional.txt +++ b/docs/guide/uk/test.functional.txt @@ -1,17 +1,29 @@ Функціональне тестування ======================== -Перед прочитанням даного розділу рекомендується прочитати [документацію по Selenium](http://seleniumhq.org/docs/) і [документацію по PHPUnit](http://www.phpunit.de/wiki/Documentation). Далі ми підсумуємо основні принципи написання функціональних тестів у Yii: +Перед прочитанням даного розділу рекомендується прочитати [документацію по Selenium](http://seleniumhq.org/docs/) +і [документацію по PHPUnit](http://www.phpunit.de/wiki/Documentation). +Далі ми підсумуємо основні принципи написання функціональних тестів у Yii: - * Як і модульний тест, функціональний тест пишеться в класі `XyzTest`, успадковує клас [CWebTestCase], де `Xyz` — імʼя класу, що підлягає тестуванню. Ми можемо використовувати всі методи класу `PHPUnit_Extensions_SeleniumTestCase`, бо він є предком класу [CWebTestCase]. + * Як і модульний тест, функціональний тест пишеться в класі `XyzTest`, +успадковує клас [CWebTestCase], де `Xyz` — імʼя класу, що підлягає тестуванню. +Ми можемо використовувати всі методи класу `PHPUnit_Extensions_SeleniumTestCase`, +бо він є предком класу [CWebTestCase]. - * Клас функціонального тесту зберігається у файлі з імʼям `XyzTest.php`. За угодою, файл функціонального тесту може зберігатися в директорії `protected/tests/functional`. + * Клас функціонального тесту зберігається у файлі з імʼям `XyzTest.php`. +За згодою, файл функціонального тесту може зберігатися в директорії `protected/tests/functional`. - * Основний вміст класу тесту — набір тестових методів з іменами `testAbc`, де `Abc` — часто імʼя тестованої особливості. Наприклад, для тестування особливості входу користувача у нас є метод `testLogin`. + * Основний вміст класу тесту — набір тестових методів з іменами `testAbc`, +де `Abc` — часто імʼя тестованої особливості. +Наприклад, для тестування особливості входу користувача у нас є метод `testLogin`. - * Тестовий метод зазвичай містить послідовність виразів, які будуть командами перевірки для Selenium RC, що показує хід і результати тестування веб-додатка. У ньому також містяться вирази тверджень для перевірки, що веб-додаток відповідає саме так, як очікувалося. + * Тестовий метод зазвичай містить послідовність виразів, які будуть командами перевірки для Selenium RC, +що показує хід і результати тестування веб-додатка. +У ньому також містяться вирази тверджень для перевірки, що веб-додаток відповідає саме так, як очікувалося. -Перед описом, як же писати функціональний тест, давайте глянемо файл `WebTestCase.php`, згенерований командою `yiic webapp`. Цей файл визначає клас `WebTestCase`, який може служити базовим для всіх класів функціональних тестів. +Перед описом, як же писати функціональний тест, давайте глянемо файл `WebTestCase.php`, +згенерований командою `yiic webapp`. Цей файл визначає клас `WebTestCase`, +який може служити базовим для всіх класів функціональних тестів. ~~~ [php] @@ -33,11 +45,16 @@ class WebTestCase extends CWebTestCase } ~~~ -Клас `WebTestCase` в основному встановлює базовий URL тестованих сторінок. Далі, в тестових методах, ми можемо використовувати відносні URL для визначення тестованих сторінок. +Клас `WebTestCase` в основному встановлює базовий URL тестованих сторінок. +Далі, в тестових методах, ми можемо використовувати відносні URL для визначення тестованих сторінок. -Ми також повинні звернути увагу, що згідно базового тестового URL у якості вхідної точки використовується файл `index-test.php` замість файлу `index.php`. Єдина відмінність між сценаріями `index-test.php` і `index.php` те, що у якості файлу конфігурації додатка перший використовує файл `test.php`, а другий — файл `main.php`. +Ми також повинні звернути увагу, що згідно базового тестового URL у якості вхідної точки використовується +файл `index-test.php` замість файлу `index.php`. Єдина відмінність між сценаріями `index-test.php` і `index.php` те, +що у якості файлу конфігурації додатка перший використовує файл `test.php`, а другий — файл `main.php`. -Тепер ми опишемо, як протестувати функцію відображення запису [демо-блога](http://www.yiiframework.com/demos/blog). Спочатку ми пишемо тестовий клас, як показано нижче. Зазначимо, що тестовий клас успадковує від базового класу, який ми тільки що описали: +Тепер ми опишемо, як протестувати функцію відображення запису [демо-блога](http://www.yiiframework.com/demos/blog). +Спочатку ми пишемо тестовий клас, як показано нижче. +Зазначимо, що тестовий клас успадковує від базового класу, який ми тільки що описали: ~~~ [php] @@ -60,6 +77,13 @@ class PostTest extends WebTestCase } ~~~ -Як і при написанні класу модульного тесту, ми оголошуємо фікстури для використання цим тестом. Тут ми показуємо, що повинна використовуватися фікстура `Post`. У тестуючому методі `testShow` ми спочатку доручаємо `Selenium RC` відкрити URL `post/1`. Зауважимо, що це відносний URL, а повний URL формується шляхом додавання відносного до базового URL (тобто `http://localhost/yii/demos/blog/index-test.php/post/1`), який ми встановили в базовому класі. Потім ми перевіряємо, що можемо знайти заголовок запису `sample1` на даній сторінці. І ми також перевіряємо, що сторінка містить текст `Leave a comment`. +Як і при написанні класу модульного тесту, ми оголошуємо фікстури для використання цим тестом. Тут ми показуємо, +що повинна використовуватися фікстура `Post`. У тестуючому методі `testShow` ми спочатку доручаємо +`Selenium RC` відкрити URL `post/1`. Зауважимо, що це відносний URL, а повний URL формується шляхом додавання +відносного до базового URL (тобто `http://localhost/yii/demos/blog/index-test.php/post/1`), який ми встановили в базовому класі. +Потім ми перевіряємо, що можемо знайти заголовок запису `sample1` на даній сторінці. +І ми також перевіряємо, що сторінка містить текст `Leave a comment`. -> Tip|Підказка: Перед запуском функціональних тестів запустіть сервер Selenium-RC. Зробити це можна командою `java -jar selenium-server.jar`, виконаної із директорії, в яку встановлено Selenium. \ No newline at end of file +> Tip|Підказка: Перед запуском функціональних тестів запустіть сервер Selenium-RC. +Зробити це можна командою `java -jar selenium-server.jar`, +виконаної із директорії, в яку встановлено Selenium. \ No newline at end of file diff --git a/docs/guide/uk/topics.auth.txt b/docs/guide/uk/topics.auth.txt index ddb1b6163f..d5f674fc8b 100644 --- a/docs/guide/uk/topics.auth.txt +++ b/docs/guide/uk/topics.auth.txt @@ -1,22 +1,40 @@ Аутентифікація і авторизація ============================ -Аутентифікація і авторизація необхідні на сторінках, доступних лише деяким користувачам. *Аутентифікація* - перевірка, чи є хтось тим, за кого себе видає. Зазвичай вона має на увазі введення логіна і пароля, але також можуть бути використані й інші засоби, такі як використання смарт-карти, відбитків пальців та ін. *Авторизація* - перевірка, чи може аутентифікований користувач виконувати певні дії (їх часто позначають як ресурси). Найчастіше це визначається перевіркою, чи призначена користувачеві певна роль, яка має доступ до ресурсів. +Аутентифікація і авторизація необхідні на сторінках, доступних лише деяким користувачам. +*Аутентифікація* - перевірка, чи є хтось тим, за кого себе видає. +Зазвичай вона має на увазі введення логіна і пароля, але також можуть бути використані й інші засоби, +такі як використання смарт-карти, відбитків пальців та ін. +*Авторизація* - перевірка, чи може аутентифікований користувач виконувати певні дії (їх часто позначають як ресурси). +Найчастіше це визначається перевіркою, чи призначена користувачеві певна роль, яка має доступ до ресурсів. У Yii вбудований зручний фреймворк аутентифікації і авторизації (auth), який, у разі необхідності, може бути налаштовано під ваші завдання. -Центральним компонентом auth-фреймворку є визначений *компонент додатку «користувач»* — обʼєкт, який реалізує інтерфейс [IWebUser]. Даний компонент містить постійну інформацію про поточного користувача. Ми можемо отримати до неї доступ з будь-якого місця програми, використовуючи `Yii::app()->user`. +Центральним компонентом auth-фреймворку є визначений *компонент додатку «користувач»* — обʼєкт, який реалізує інтерфейс [IWebUser]. +Даний компонент містить постійну інформацію про поточного користувача. +Ми можемо отримати до неї доступ з будь-якого місця програми, використовуючи `Yii::app()->user`. -Використовуючи цей компонент, ми можемо перевірити, чи аутентифікований користувач, використовуючи [CWebUser::isGuest]. Ми можемо зробити [вхід|CWebUser::login] або [вихід|CWebUser::logout]. Для перевірки прав на певні дії зручно скористатися [CWebUser::checkAccess]. Також є можливість отримати [унікальний ідентифікатор|CWebUser::name] та інші постійні дані користувача. +Використовуючи цей компонент, ми можемо перевірити, чи аутентифікований користувач, використовуючи [CWebUser::isGuest]. +Ми можемо зробити [вхід|CWebUser::login] або [вихід|CWebUser::logout]. +Для перевірки прав на певні дії зручно скористатися [CWebUser::checkAccess]. +Також є можливість отримати [унікальний ідентифікатор|CWebUser::name] та інші постійні дані користувача. Визначення класу Identity ------------------------- -Як було згадано раніше, аутентифікація - це процес перевірки особистості користувача. Типовий веб-додаток для такої перевірки зазвичай використовує логін і пароль. Тим не менш, може знадобитися реалізувати перевірку іншими методами. Щоб додати підтримку різних методів аутентифікації, в Yii є відповідний identity клас. +Як було згадано раніше, аутентифікація - це процес перевірки особистості користувача. +Типовий веб-додаток для такої перевірки зазвичай використовує логін і пароль. +Тим не менш, може знадобитися реалізувати перевірку іншими методами. +Щоб додати підтримку різних методів аутентифікації, в Yii є відповідний identity клас. -Ми реалізуємо клас identity, який містить потрібну нам логіку аутентифікації. Такий клас повинен реалізувати інтерфейс [IUserIdentity]. Для різних підходів до аутентифікації можуть бути реалізовані різні класи (наприклад, OpenID, LDAP, Twitter OAuth або Facebook Connect). При створенні своєї реалізації необхідно розширити клас [CUserIdentity], який є базовим класом, який реалізує перевірку за логіном і паролем. +Ми реалізуємо клас identity, який містить потрібну нам логіку аутентифікації. +Такий клас повинен реалізувати інтерфейс [IUserIdentity]. +Для різних підходів до аутентифікації можуть бути реалізовані різні класи (наприклад, OpenID, LDAP, Twitter OAuth або Facebook Connect). +При створенні своєї реалізації необхідно розширити клас [CUserIdentity], який є базовим класом, який реалізує перевірку за логіном і паролем. -Головне завдання при створенні класу Identity — реалізація методу [IUserIdentity::authenticate]. Даний метод використовується для опису основного алгоритму аутентифікації. Також, даний клас може містити додаткову інформацію про користувача, яка необхідна нам в процесі роботи з його сесією. +Головне завдання при створенні класу Identity — реалізація методу [IUserIdentity::authenticate]. +Даний метод використовується для опису основного алгоритму аутентифікації. +Також, даний клас може містити додаткову інформацію про користувача, яка необхідна нам в процесі роботи з його сесією. #### Приклад @@ -40,7 +58,7 @@ class UserIdentity extends CUserIdentity $record=User::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID; - else if($record->password!==crypt($this->password, $record->password)) + else if(!CPasswordHelper::verifyPassword($this->password, $record->password)) $this->errorCode=self::ERROR_PASSWORD_INVALID; else { @@ -58,9 +76,16 @@ class UserIdentity extends CUserIdentity } ~~~ -У наступному підрозділі ми розглянемо реалізацію входу і виходу, використовуючи наш identity клас у методі `login` користувача. Вся інформація, яку ми зберігаємо у станах (шляхом виклику [CBaseUserIdentity::setState]) буде передана у [CWebUser], який, у свою чергу, буде зберігати її в постійному сховищі, такому як сесії. До даної інформації можна буде звертатися як до властивостей [CWebUser]. У нашому прикладі ми зберегли імʼя користувача, використовуючи `$this->setState('title', $record->title);`. Як тільки користувач успішно увійде у додаток, ми зможемо отримати його `title` використовуючи `Yii::app()->user->title`. +У наступному підрозділі ми розглянемо реалізацію входу і виходу, використовуючи наш identity клас у методі `login` користувача. +Вся інформація, яку ми зберігаємо у станах (шляхом виклику [CBaseUserIdentity::setState]) буде передана у [CWebUser], +який, у свою чергу, буде зберігати її в постійному сховищі, такому як сесії. +До даної інформації можна буде звертатися як до властивостей [CWebUser]. +У нашому прикладі ми зберегли імʼя користувача, використовуючи `$this->setState('title', $record->title);`. +Як тільки користувач успішно увійде у додаток, ми зможемо отримати його `title` використовуючи `Yii::app()->user->title`. -> Info|Інформація: За замовчуванням [CWebUser] використовує сесії для зберігання даних. Якщо ви використовуєте автоматичний вхід користувача за допомогою cookie ([CWebUser::allowAutoLogin] встановлений у `true`), дані користувача будуть також зберігатися у cookie. Переконайтеся, що ці дані не містять конфіденційної інформації, такої як паролі. +> Info|Інформація: За замовчуванням [CWebUser] використовує сесії для зберігання даних. +Якщо ви використовуєте автоматичний вхід користувача за допомогою cookie ([CWebUser::allowAutoLogin] встановлений у `true`), +дані користувача будуть також зберігатися у cookie. Переконайтеся, що ці дані не містять конфіденційної інформації, такої як паролі. ### Зберігання паролів у базі даних @@ -69,13 +94,8 @@ class UserIdentity extends CUserIdentity використовуючи стандартні підходи, у випадку, якщо ви не передбачите захист від них. Зокрема, ви повинні додавати "сіль" до паролю перед хешуванням і використовувати хеш-функцію, яка забере у нападника багато часу для обчислення. -Наведений вище приклад коду використовує вбудовану в PHP функцію `crypt()`, яка, -із відповідним використання, повертає хеш, який дуже важко зламати. - -Щоб дізнатися більше про ці теми, будь-ласка, прочитайте: - -- [Функція PHP `crypt()`](http://php.net/manual/ru/function.crypt.php) -- Yii wiki-стаття [Використання crypt() для зберігання паролів](http://www.yiiframework.com/wiki/425) +Наведений вище приклад коду використовує вбудований помічник Yii [CPasswordHelper] для хешування та валідації пароля (починаючи із версії 1.1.14). +[CPasswordHelper::hashPassword] повертає достатньо стійкий хеш. Вхід и вихід ------------ @@ -95,14 +115,26 @@ else Yii::app()->user->logout(); ~~~ -Ми створюємо новий обʼєкт UserIdentity і передаємо в його конструктор параметри аутентифікації (тобто `$username` і `$password`, введені користувачем). Далі просто викликаємо метод `authenticate()`. У разі успішної перевірки даних ми передаємо обʼєкт в метод [CWebUser::login], який зберігає інформацію в постійному сховищі (за замовчуванням у сесіях PHP) і робить її доступною у наступних запитах. Якщо аутентифікація не проходить, ми можемо отримати інформацію про помилку із властивості `errorMessage`. +Ми створюємо новий обʼєкт UserIdentity і передаємо в його конструктор параметри аутентифікації (тобто `$username` і `$password`, введені користувачем). +Далі просто викликаємо метод `authenticate()`. У разі успішної перевірки даних ми передаємо обʼєкт в метод [CWebUser::login], +який зберігає інформацію в постійному сховищі (за замовчуванням у сесіях PHP) і робить її доступною у наступних запитах. +Якщо аутентифікація не проходить, ми можемо отримати інформацію про помилку із властивості `errorMessage`. -Перевірити, чи є користувач аутентифікованим, дуже просто. Для цього можна скористатися `Yii::app()->user->isGuest`. При використанні постійного сховища, такого як сесії (за замовчуванням) та/або cookie (описано нижче), для зберігання інформації про користувача, користувач може залишатися аутентифікованим у наступних запитах. У цьому випадку немає необхідності використовувати клас UserIdentity і показувати форму входу. [CWebUser] автоматично завантажить необхідну інформацію із постійного сховища і використовує її при зверненні до `Yii::app()->user->isGuest`. +Перевірити, чи є користувач аутентифікованим, дуже просто. Для цього можна скористатися `Yii::app()->user->isGuest`. +При використанні постійного сховища, такого як сесії (за замовчуванням) та/або cookie (описано нижче), +для зберігання інформації про користувача, користувач може залишатися аутентифікованим у наступних запитах. +У цьому випадку немає необхідності використовувати клас UserIdentity і показувати форму входу. +[CWebUser] автоматично завантажить необхідну інформацію із постійного сховища і використовує її при зверненні до `Yii::app()->user->isGuest`. Вхід на основі cookie --------------------- -За замовчуванням, після деякого часу бездіяльності, що залежить від [налаштувань сесії](http://php.net/manual/en/session.configuration.php), буде проведений вихід із системи. Для того, щоб цього не відбувалося, необхідно виставити властивості компонента User [allowAutoLogin|CWebUser::allowAutoLogin] в `true` і передати необхідний час життя cookie у метод [CWebUser::login]. Користувач буде автоматично аутентифікований на сайті протягом зазначеного часу навіть у тому випадку, якщо він закриє браузер. Дана можливість вимагає підтримки cookie в браузері користувача. +За замовчуванням, після деякого часу бездіяльності, що залежить від [налаштувань сесії](http://php.net/manual/en/session.configuration.php), +буде проведений вихід із системи. Для того, щоб цього не відбувалося, +необхідно виставити властивості компонента User [allowAutoLogin|CWebUser::allowAutoLogin] в `true` +і передати необхідний час життя cookie у метод [CWebUser::login]. +Користувач буде автоматично аутентифікований на сайті протягом зазначеного часу навіть у тому випадку, якщо він закриє браузер. +Дана можливість вимагає підтримки cookie в браузері користувача. ~~~ [php] @@ -202,9 +234,13 @@ class PostController extends CController ~~~ Наведений код описує три правила, кожне з яких представлено у вигляді масиву. Перший елемент масиву може приймати значення `'allow'` або `'deny'`. -Решта пар ключ-значення задають параметри правила. Правила, задані вище, можна прочитати таким чином: дії `create` і `edit` не можуть бути виконані анонімними користувачами, а дія `delete` може бути виконана тільки користувачами із роллю `admin`. +Решта пар ключ-значення задають параметри правила. +Правила, задані вище, можна прочитати таким чином: дії `create` і `edit` не можуть бути виконані анонімними користувачами, +а дія `delete` може бути виконана тільки користувачами із роллю `admin`. -Правила доступу розбираються по черзі у порядку їх опису. Перше правило, яке збігається із поточними даними (наприклад, з імʼям користувача, роллю або IP) визначає результат авторизації. Якщо це дозволяє правило, дія може бути виконана, якщо забороняє - не може. Якщо жодне з правил не співпало - дія може бути виконана. +Правила доступу розбираються по черзі у порядку їх опису. Перше правило, +яке збігається із поточними даними (наприклад, з імʼям користувача, роллю або IP) визначає результат авторизації. +Якщо це дозволяє правило, дія може бути виконана, якщо забороняє - не може. Якщо жодне з правил не співпало - дія може бути виконана. > Tip|Підказка: Щоб бути впевненим, що дія не буде виконана, > необхідно заборонити всі дії, які не дозволені, визначивши відповідне @@ -228,7 +264,8 @@ class PostController extends CController - [controllers|CAccessRule::controllers]: дозволяє вказати контролери у вигляді масиву їх ідентифікаторів. Порівняння регістронезалежне; - - [users|CAccessRule::users]: дозволяє вказати користувачів. Для порівняння використовується [CWebUser::name]. Порівняння регістронезалежне.У параметрі можуть бути використані наступні спеціальні символи: + - [users|CAccessRule::users]: дозволяє вказати користувачів. Для порівняння використовується [CWebUser::name]. + Порівняння регістронезалежне.У параметрі можуть бути використані наступні спеціальні символи: - `*`: будь-який користувач, включаючи анонімного; - `?`: анонімний користувач; @@ -238,14 +275,16 @@ class PostController extends CController [доступ на основі ролей](/doc/guide/topics.auth#sec5), описаний у наступному розділі. У окремому випадку, право застосується, якщо [CWebUser::checkAccess] поверне `true` для однієї з ролей. Ролі варто використовувати в дозвільних правилах, так як роль асоціюється із можливістю виконання якої-небудь дії. -Також варто відзначити, що, незважаючи на те, що ми використовуємо термін «роль», значенням може бути будь-який елемент auth-фреймворку, такий як ролі, завдання або операції; +Також варто відзначити, що, незважаючи на те, що ми використовуємо термін «роль», +значенням може бути будь-який елемент auth-фреймворку, такий як ролі, завдання або операції; - [ips|CAccessRule::ips]: дозволяє вказати IP-адрес; - [verbs|CAccessRule::verbs]: дозволяє вказати тип запитів (наприклад, `GET` або `POST`). Порівняння регістронезалежне; - - [expression|CAccessRule::expression]: дозволяє вказати вираз PHP, обчислення якого буде визначати збіг правила. Усередині виразу доступна змінна `$user`, яка вказує на `Yii::app()->user`. + - [expression|CAccessRule::expression]: дозволяє вказати вираз PHP, обчислення якого буде визначати збіг правила. + Усередині виразу доступна змінна `$user`, яка вказує на `Yii::app()->user`. Обробка запиту авторизації @@ -277,7 +316,9 @@ array( ) ~~~ -Якщо браузер був перенаправлений на сторінку входу і вхід вдалий, вам може знадобитися перенаправити користувача до тієї сторінці, на якій невдало пройшла авторизація. Як же дізнатися URL тієї сторінки? Ми можемо отримати цю інформацію з властивості [returnUrl|CWebUser::returnUrl] компонента `user`. +Якщо браузер був перенаправлений на сторінку входу і вхід вдалий, вам може знадобитися перенаправити користувача до тієї сторінці, +на якій невдало пройшла авторизація. Як же дізнатися URL тієї сторінки? +Ми можемо отримати цю інформацію з властивості [returnUrl|CWebUser::returnUrl] компонента `user`. Маючи її, ми можемо зробити перенаправлення: ~~~ @@ -288,7 +329,8 @@ Yii::app()->request->redirect(Yii::app()->user->returnUrl); Контроль доступу на основі ролей -------------------------------- -Контроль доступу на основі ролей (RBAC) —  простий, але потужний спосіб централізованого контролю доступу. Для порівняння даного методу з іншими зверніться до +Контроль доступу на основі ролей (RBAC) —  простий, але потужний спосіб централізованого контролю доступу. +Для порівняння даного методу з іншими зверніться до [статті у Вікіпедії](http://ru.wikipedia.org/wiki/%D3%EF%F0%E0%E2%EB%E5%ED%E8%E5_%E4%EE%F1%F2%F3%EF%EE%EC_%ED%E0_%EE%F1%ED%EE%E2%E5_%F0%EE%EB%E5%E9). В Yii ієрархічний RBAC реалізований через компонент [authManager|CWebApplication::authManager]. @@ -300,18 +342,26 @@ Yii::app()->request->redirect(Yii::app()->user->returnUrl); Основним поняттям у RBAC Yii є *елемент авторизації*. Елемент авторизації — це права на виконання якої-небудь дії (створити новий запис у блозі, управління користувачами). В залежності від структури і цілі, елементи авторизації можуть бути розділені на *операції*, *завдання* і *ролі*. -Роль складається із завдань. Завдання складається з операцій. Операція - дозвіл на будь-яку дію (далі не ділиться). Наприклад, у системі може бути роль `адміністратор`, що складається із завдань `керування записами` і `керування користувачами`. Задача `керування користувачами` може складатися з операцій `створити користувача`, `редагувати користувача` і `видалити користувача`. Для досягнення більшої гнучкості, роль в Yii може складатися з інших ролей і операцій. Завдання може складатися з інших завдань. Операція - з інших операцій. +Роль складається із завдань. Завдання складається з операцій. Операція - дозвіл на будь-яку дію (далі не ділиться). +Наприклад, у системі може бути роль `адміністратор`, що складається із завдань `керування записами` і `керування користувачами`. +Задача `керування користувачами` може складатися з операцій `створити користувача`, `редагувати користувача` і `видалити користувача`. +Для досягнення більшої гнучкості, роль в Yii може складатися з інших ролей і операцій. Завдання може складатися з інших завдань. Операція - з інших операцій. Елемент авторизації однозначно ідентифікується його унікальним імʼям. -Елемент авторизації може бути асоційований із *бізнес-правилом* — PHP-кодом, який буде використовуватися при перевірці доступу. Користувач отримає доступ до елемента тільки якщо код поверне `true`. -Наприклад, при визначенні операції `updatePost`, буде не зайвим додати бізнес-правило, що перевіряє відповідність ID користувача ID автора запису. Тобто, доступ до редагування запису має тільки її автор. +Елемент авторизації може бути асоційований із *бізнес-правилом* — PHP-кодом, який буде використовуватися при перевірці доступу. +Користувач отримає доступ до елемента тільки якщо код поверне `true`. +Наприклад, при визначенні операції `updatePost`, буде не зайвим додати бізнес-правило, що перевіряє відповідність ID користувача ID автора запису. +Тобто, доступ до редагування запису має тільки її автор. Використовуючи елементи авторизації, ми можемо побудувати *ієрархію авторизації*. -Елемент `A` є батьком елемента `B` в ієрархії, якщо `A` складається із `B` (або `A` успадковує права, представлені в `B`). Елемент може мати кілька нащадків і кілька предків. Тому ієрархія авторизації є скоріше частково упорядкованим графом, ніж деревом. У ній ролі перебувають на верхніх рівнях, операції - на нижніх. Посередині розташовані завдання. +Елемент `A` є батьком елемента `B` в ієрархії, якщо `A` складається із `B` (або `A` успадковує права, представлені в `B`). +Елемент може мати кілька нащадків і кілька предків. Тому ієрархія авторизації є скоріше частково упорядкованим графом, ніж деревом. +У ній ролі перебувають на верхніх рівнях, операції - на нижніх. Посередині розташовані завдання. Після побудови ієрархії авторизації, ми можемо призначати ролі із неї користувачам нашого додатку. -Користувач отримує всі права ролі, яка йому призначена. Приміром, якщо призначити користувачу роль `адміністратор`, він отримає адміністративні повноваження, такі як `керування записами` або `керування користувачами` (і відповідні їм операції, такі як `створити користувача`). +Користувач отримує всі права ролі, яка йому призначена. Приміром, якщо призначити користувачу роль `адміністратор`, +він отримає адміністративні повноваження, такі як `керування записами` або `керування користувачами` (і відповідні їм операції, такі як `створити користувача`). А тепер найприємніше. У дії контролера хочемо перевірити, чи може поточний користувач видалити певний запис. При використанні ієрархії RBAC і призначеної користувачеві ролі, це робиться дуже просто: @@ -330,7 +380,8 @@ if(Yii::app()->user->checkAccess('deletePost')) Перед тим, як ми перейдемо до побудови ієрархії авторизації і безпосередньо перевірці доступу, нам буде потрібно налаштувати компонент додатку [authManager|CWebApplication::authManager]. В Yii є два типи менеджерів авторизації: [CPhpAuthManager] та [CDbAuthManager]. Перший використовує для зберігання даних PHP, другий - базу даних. -При налаштуванні [authManager|CWebApplication::authManager] необхідно вказати, який з компонентів ми збираємося використовувати і вказати початкові значення властивостей компонента. Приміром: +При налаштуванні [authManager|CWebApplication::authManager] необхідно вказати, +який з компонентів ми збираємося використовувати і вказати початкові значення властивостей компонента. Приміром: ~~~ [php] @@ -427,7 +478,8 @@ $auth->assign('admin','adminD'); При побудові ієрархії авторизації ми можемо призначити роль, завдання або операцію *бізнес-правилу*. Також ми можемо вказати його при призначенні ролі користувачу. -Бізнес-правило - PHP-код, що використовується при перевірці доступу. Значення, яке повертає даний код, визначає, чи застосовувати дану роль до активного користувача. +Бізнес-правило - PHP-код, що використовується при перевірці доступу. +Значення, яке повертає даний код, визначає, чи застосовувати дану роль до активного користувача. У прикладі вище ми застосували бізнес-правило для опису завдання `updateOwnPost`. У ньому ми перевіряємо, чи збігається ID поточного користувача із ID автора запису. Інформація про запис в масиві `$params` передається розробником при перевірці доступу. @@ -447,7 +499,8 @@ if(Yii::app()->user->checkAccess('createPost')) } ~~~ -Якщо правило авторизації використовує бізнес-правило, що вимагає додаткових параметрів, необхідно їх передати. Наприклад, щоб перевірити, чи може користувач редагувати запис, ми передаємо дані про запис в `$params`: +Якщо правило авторизації використовує бізнес-правило, що вимагає додаткових параметрів, необхідно їх передати. +Наприклад, щоб перевірити, чи може користувач редагувати запис, ми передаємо дані про запис в `$params`: ~~~ [php] @@ -486,7 +539,8 @@ return array( ); ~~~ -Так як роль за умовчанням призначається кожному користувачу, звичайно потрібно використовувати бізнес-правило, що визначає, до яких саме користувачів її застосовувати. Наприклад, наступний код визначає дві ролі: +Так як роль за умовчанням призначається кожному користувачу, звичайно потрібно використовувати бізнес-правило, +що визначає, до яких саме користувачів її застосовувати. Наприклад, наступний код визначає дві ролі: `authenticated` і `guest`, які відповідно застосовуються до аутентифікованих користувачів та гостей. ~~~ @@ -498,4 +552,7 @@ $bizRule='return Yii::app()->user->isGuest;'; $auth->createRole('guest', 'гість', $bizRule); ~~~ -> Info|Інформація: Починаючи із версії 1.1.11 масив `$params`, який передається до бізнес правила, має ключ `userId`, значення цього ключа це id користувача, для якого ми перевіряємо бізнес правило. Вам було б це потрібно, якщо б ви викликали [CDbAuthManager::checkAccess()] або [CPhpAuthManager::checkAccess()] у місцях, де `Yii::app()->user` відсутній або перевіряєте доступ для іншого користувача. \ No newline at end of file +> Info|Інформація: Починаючи із версії 1.1.11 масив `$params`, який передається до бізнес правила, має ключ `userId`, +значення цього ключа це id користувача, для якого ми перевіряємо бізнес правило. +Вам було б це потрібно, якщо б ви викликали [CDbAuthManager::checkAccess()] або [CPhpAuthManager::checkAccess()] у місцях, +де `Yii::app()->user` відсутній або перевіряєте доступ для іншого користувача. \ No newline at end of file diff --git a/docs/guide/uk/topics.i18n.txt b/docs/guide/uk/topics.i18n.txt index b3bb082814..aa44d9d26d 100644 --- a/docs/guide/uk/topics.i18n.txt +++ b/docs/guide/uk/topics.i18n.txt @@ -154,9 +154,9 @@ Yii підтримує [формат вибору|CChoiceFormat], відомий ~~~ [php] -Yii::t('app', 'n==1#one book|n>1#many books', array(1))); +Yii::t('app', 'n==1#one book|n>1#many books', array(1)); //or since 1.1.6 -Yii::t('app', 'n==1#one book|n>1#many books', 1)); +Yii::t('app', 'n==1#one book|n>1#many books', 1); ~~~ Якщо перевіряється відповідність певному числу, можна використовувати скорочену запис, який буде розглядатися як `n==Number`: diff --git a/docs/guide/zh_cn/basics.component.txt b/docs/guide/zh_cn/basics.component.txt index a340a2531c..e28ec4a21a 100644 --- a/docs/guide/zh_cn/basics.component.txt +++ b/docs/guide/zh_cn/basics.component.txt @@ -95,7 +95,7 @@ $component->onClicked=function($event) { 附属的事件句柄将被自动调用。 一个事件可以绑定多个句柄。当事件触发时, -这些句柄将被按照它们绑定到事件时的顺序依次执行。如果句柄决定组织后续句柄被执行,它可以设置 +这些句柄将被按照它们绑定到事件时的顺序依次执行。如果句柄决定阻止后续句柄被执行,它可以设置 [$event->handled|CEvent::handled] 为 true。 @@ -131,7 +131,7 @@ $behavior=$component->tree; // $behavior=$component->asa('tree'); ~~~ -行为是可以被临时禁止的,此时它的方法开就会在组件中失效.例如: +行为是可以被临时禁止的,此时它的方法就会在组件中失效.例如: ~~~ [php] @@ -153,4 +153,4 @@ $component->test(); 这些属性包含公共成员变量以及通过 getters 和/或 setters 方式设置的属性。 例如, 若一个行为有一个 xyz 的属性,此行为被绑定到组件 $a,然后我们可以使用表达式 `$a->xyz` 访问此行为的属性。 -
$Id: basics.component.txt 2346 2010-08-28 13:12:27Z mdomba $
\ No newline at end of file +
$Id: basics.component.txt 2346 2010-08-28 13:12:27Z mdomba $
diff --git a/framework/YiiBase.php b/framework/YiiBase.php index 658c0621af..115d44b872 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -80,7 +80,7 @@ class YiiBase */ public static function getVersion() { - return '1.1.14-dev'; + return '1.1.15-dev'; } /** @@ -287,13 +287,20 @@ public static function import($alias,$forceInclude=false) return $alias; } else - throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory.', - array('{alias}'=>$namespace))); + { + // try to autoload the class with an autoloader + if (class_exists($alias,true)) + return self::$_imports[$alias]=$alias; + else + throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.', + array('{alias}'=>$namespace))); + } } if(($pos=strrpos($alias,'.'))===false) // a simple class name { - if($forceInclude && self::autoload($alias)) + // try to autoload the class with an autoloader if $forceInclude is true + if($forceInclude && (Yii::autoload($alias,true) || class_exists($alias,true))) self::$_imports[$alias]=$alias; return $alias; } @@ -386,15 +393,19 @@ public static function setPathOfAlias($alias,$path) * Class autoload loader. * This method is provided to be invoked within an __autoload() magic method. * @param string $className class name + * @param bool $classMapOnly whether to load classes via classmap only * @return boolean whether the class has been loaded successfully + * @throws CException When class name does not match class file in debug mode. */ - public static function autoload($className) + public static function autoload($className,$classMapOnly=false) { // use include so that the error PHP file may appear if(isset(self::$classMap[$className])) include(self::$classMap[$className]); elseif(isset(self::$_coreClasses[$className])) include(YII_PATH.self::$_coreClasses[$className]); + elseif($classMapOnly) + return false; else { // include class file relying on include_path diff --git a/framework/base/CApplication.php b/framework/base/CApplication.php index 0219478710..31503ab3ad 100644 --- a/framework/base/CApplication.php +++ b/framework/base/CApplication.php @@ -42,7 +42,7 @@ * CApplication will undergo the following lifecycles when processing a user request: *
    *
  1. load application configuration;
  2. - *
  3. set up class autoloader and error handling;
  4. + *
  5. set up error handling;
  6. *
  7. load static application components;
  8. *
  9. {@link onBeginRequest}: preprocess the user request;
  10. *
  11. {@link processRequest}: process the user request;
  12. @@ -937,7 +937,7 @@ public function displayException($exception) } /** - * Initializes the class autoloader and error handlers. + * Initializes the error handlers. */ protected function initSystemHandlers() { diff --git a/framework/base/CComponent.php b/framework/base/CComponent.php index 103be3538a..13d81f5a30 100644 --- a/framework/base/CComponent.php +++ b/framework/base/CComponent.php @@ -261,7 +261,7 @@ public function __call($name,$parameters) return call_user_func_array(array($object,$name),$parameters); } } - if(class_exists('Closure', false) && $this->canGetProperty($name) && $this->$name instanceof Closure) + if(class_exists('Closure', false) && ($this->canGetProperty($name) || property_exists($this, $name)) && $this->$name instanceof Closure) return call_user_func_array($this->$name, $parameters); throw new CException(Yii::t('yii','{class} and its behaviors do not have a method or closure named "{name}".', array('{class}'=>get_class($this), '{name}'=>$name))); @@ -499,7 +499,7 @@ public function getEventHandlers($name) * $component->getEventHandlers($eventName)->add($eventHandler); * * - * Using {@link getEventHandlers}, one can also specify the excution order + * Using {@link getEventHandlers}, one can also specify the execution order * of multiple handlers attaching to the same event. For example: *
     	 * $component->getEventHandlers($eventName)->insertAt(0,$eventHandler);
    diff --git a/framework/base/CErrorHandler.php b/framework/base/CErrorHandler.php
    index 3ece1b2c1b..b728470fa8 100644
    --- a/framework/base/CErrorHandler.php
    +++ b/framework/base/CErrorHandler.php
    @@ -48,6 +48,7 @@
      * {@link CApplication::getErrorHandler()}.
      *
      * @property array $error The error details. Null if there is no error.
    + * @property Exception|null $exception exception instance. Null if there is no exception.
      *
      * @author Qiang Xue 
      * @package system.base
    @@ -82,6 +83,7 @@ class CErrorHandler extends CApplicationComponent
     	public $errorAction;
     
     	private $_error;
    +	private $_exception;
     
     	/**
     	 * Handles the exception/error event.
    @@ -150,6 +152,15 @@ public function getError()
     		return $this->_error;
     	}
     
    +	/**
    +	 * Returns the instance of the exception that is currently being handled.
    +	 * @return Exception|null exception instance. Null if there is no exception.
    +	 */
    +	public function getException()
    +	{
    +		return $this->_exception;
    +	}
    +
     	/**
     	 * Handles the exception.
     	 * @param Exception $exception the exception captured
    @@ -186,6 +197,7 @@ protected function handleException($exception)
     				unset($trace[$i]['object']);
     			}
     
    +			$this->_exception=$exception;
     			$this->_error=$data=array(
     				'code'=>($exception instanceof CHttpException)?$exception->statusCode:500,
     				'type'=>get_class($exception),
    @@ -200,15 +212,7 @@ protected function handleException($exception)
     			if(!headers_sent())
     				header("HTTP/1.0 {$data['code']} ".$this->getHttpHeader($data['code'], get_class($exception)));
     
    -			if($exception instanceof CHttpException || !YII_DEBUG)
    -				$this->render('error',$data);
    -			else
    -			{
    -				if($this->isAjaxRequest())
    -					$app->displayException($exception);
    -				else
    -					$this->render('exception',$data);
    -			}
    +			$this->renderException();
     		}
     		else
     			$app->displayException($exception);
    @@ -270,7 +274,8 @@ protected function handleError($event)
     				default:
     					$type = 'PHP error';
     			}
    -			$this->_error=$data=array(
    +			$this->_exception=null;
    +			$this->_error=array(
     				'code'=>500,
     				'type'=>$type,
     				'message'=>$event->message,
    @@ -281,12 +286,7 @@ protected function handleError($event)
     			);
     			if(!headers_sent())
     				header("HTTP/1.0 500 Internal Server Error");
    -			if($this->isAjaxRequest())
    -				$app->displayError($event->code,$event->message,$event->file,$event->line);
    -			elseif(YII_DEBUG)
    -				$this->render('exception',$data);
    -			else
    -				$this->render('error',$data);
    +			$this->renderError();
     		}
     		else
     			$app->displayError($event->code,$event->message,$event->file,$event->line);
    @@ -327,15 +327,47 @@ protected function getExactTrace($exception)
     	 */
     	protected function render($view,$data)
     	{
    -		if($view==='error' && $this->errorAction!==null)
    +		$data['version']=$this->getVersionInfo();
    +		$data['time']=time();
    +		$data['admin']=$this->adminInfo;
    +		include($this->getViewFile($view,$data['code']));
    +	}
    +
    +	/**
    +	 * Renders the exception information.
    +	 * This method will display information from current {@link error} value.
    +	 */
    +	protected function renderException()
    +	{
    +		$exception=$this->getException();
    +		if($exception instanceof CHttpException || !YII_DEBUG)
    +			$this->renderError();
    +		else
    +		{
    +			if($this->isAjaxRequest())
    +				Yii::app()->displayException($exception);
    +			else
    +				$this->render('exception',$this->getError());
    +		}
    +	}
    +
    +	/**
    +	 * Renders the current error information.
    +	 * This method will display information from current {@link error} value.
    +	 */
    +	protected function renderError()
    +	{
    +		if($this->errorAction!==null)
     			Yii::app()->runController($this->errorAction);
     		else
     		{
    -			// additional information to be passed to view
    -			$data['version']=$this->getVersionInfo();
    -			$data['time']=time();
    -			$data['admin']=$this->adminInfo;
    -			include($this->getViewFile($view,$data['code']));
    +			$data=$this->getError();
    +			if($this->isAjaxRequest())
    +				Yii::app()->displayError($data['code'],$data['message'],$data['file'],$data['line']);
    +			elseif(YII_DEBUG)
    +				$this->render('exception',$data);
    +			else
    +				$this->render('error',$data);
     		}
     	}
     
    diff --git a/framework/base/CModel.php b/framework/base/CModel.php
    index 4eb2589735..3bdef0466d 100644
    --- a/framework/base/CModel.php
    +++ b/framework/base/CModel.php
    @@ -64,7 +64,7 @@ abstract public function attributeNames();
     	 * 
  13. except: this specifies the scenarios when the validation rule should not be performed. * Separate different scenarios with commas. Please see {@link scenario} for more details about this option.
  14. *
  15. additional parameters are used to initialize the corresponding validator properties. - * Please refer to individal validator class API for possible properties.
  16. + * Please refer to individual validator class API for possible properties. * * * The following are some examples: diff --git a/framework/base/CSecurityManager.php b/framework/base/CSecurityManager.php index e3a77333ac..e4c4640257 100644 --- a/framework/base/CSecurityManager.php +++ b/framework/base/CSecurityManager.php @@ -345,7 +345,17 @@ public function generateRandomString($length,$cryptographicallyStrong=true) /** * Generates a string of random bytes. * @param integer $length number of random bytes to be generated. - * @param boolean $cryptographicallyStrong whether generated string should be cryptographically strong. + * @param boolean $cryptographicallyStrong whether to fail if a cryptographically strong + * result cannot be generated. The method attempts to read from a cryptographically strong + * pseudorandom number generator (CS-PRNG), see + * {@link https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Requirements Wikipedia}. + * However, in some runtime environments, PHP has no access to a CS-PRNG, in which case + * the method returns false if $cryptographicallyStrong is true. When $cryptographicallyStrong is false, + * the method always returns a pseudorandom result but may fall back to using {@link generatePseudoRandomBlock}. + * This method does not guarantee that entropy, from sources external to the CS-PRNG, was mixed into + * the CS-PRNG state between each successive call. The caller can therefore expect non-blocking + * behavior, unlike, for example, reading from /dev/random on Linux, see + * {@link http://eprint.iacr.org/2006/086.pdf Gutterman et al 2006}. * @return boolean|string generated random binary string or false on failure. * @since 1.1.14 */ diff --git a/framework/base/interfaces.php b/framework/base/interfaces.php index d879d07ff5..052992ce9a 100644 --- a/framework/base/interfaces.php +++ b/framework/base/interfaces.php @@ -350,7 +350,7 @@ public function checkAccess($itemName,$userId,$params=array()); * Creates an authorization item. * An authorization item represents an action permission (e.g. creating a post). * It has three types: operation, task and role. - * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * Authorization items form a hierarchy. Higher level items inherit permissions representing * by lower level items. * @param string $name the item name. This must be a unique identifier. * @param integer $type the item type (0: operation, 1: task, 2: role). diff --git a/framework/caching/CApcCache.php b/framework/caching/CApcCache.php index ffc3235ebf..0fbbb1f20e 100644 --- a/framework/caching/CApcCache.php +++ b/framework/caching/CApcCache.php @@ -103,6 +103,9 @@ protected function deleteValue($key) */ protected function flushValues() { + if(extension_loaded('apcu')) + return apc_clear_cache(); + return apc_clear_cache('user'); } } diff --git a/framework/caching/CFileCache.php b/framework/caching/CFileCache.php index f8ed789663..0605398a62 100644 --- a/framework/caching/CFileCache.php +++ b/framework/caching/CFileCache.php @@ -30,10 +30,24 @@ class CFileCache extends CCache * using 'protected/runtime/cache' as the directory. */ public $cachePath; + /** + * @var integer the permission to be set for directory to store cache files + * This value will be used by PHP chmod function. + * Defaults to 0777, meaning the directory can be read, written and executed by all users. + * @since 1.1.15 + */ + public $cachePathMode=0777; /** * @var string cache file suffix. Defaults to '.bin'. */ public $cacheFileSuffix='.bin'; + /** + * @var integer the permission to be set for new cache files. + * This value will be used by PHP chmod function. + * Defaults to 0666, meaning the file is read-writable by all users. + * @since 1.1.15 + */ + public $cacheFileMode=0666; /** * @var integer the level of sub-directories to store cache files. Defaults to 0, * meaning no sub-directories. If the system has huge number of cache files (e.g. 10K+), @@ -64,7 +78,10 @@ public function init() if($this->cachePath===null) $this->cachePath=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'cache'; if(!is_dir($this->cachePath)) - mkdir($this->cachePath,0777,true); + { + mkdir($this->cachePath,$this->cachePathMode,true); + chmod($this->cachePath,$this->cachePathMode); + } } /** @@ -142,10 +159,14 @@ protected function setValue($key,$value,$expire) $cacheFile=$this->getCacheFile($key); if($this->directoryLevel>0) - @mkdir(dirname($cacheFile),0777,true); + { + $cacheDir=dirname($cacheFile); + @mkdir($cacheDir,$this->cachePathMode,true); + @chmod($cacheDir,$this->cachePathMode); + } if(@file_put_contents($cacheFile,$this->embedExpiry ? $expire.$value : $value,LOCK_EX)!==false) { - @chmod($cacheFile,0777); + @chmod($cacheFile,$this->cacheFileMode); return $this->embedExpiry ? true : @touch($cacheFile,$expire); } else diff --git a/framework/caching/CRedisCache.php b/framework/caching/CRedisCache.php index aaeaab5843..844d850bdf 100644 --- a/framework/caching/CRedisCache.php +++ b/framework/caching/CRedisCache.php @@ -148,8 +148,15 @@ private function parseResponse() case '$': // Bulk replies if($line=='-1') return null; - if(($data=fread($this->_socket,$line+2))===false) - throw new CException('Failed reading data from redis connection socket.'); + $length=$line+2; + $data=''; + while($length>0) + { + if(($block=fread($this->_socket,$length))===false) + throw new CException('Failed reading data from redis connection socket.'); + $data.=$block; + $length-=(function_exists('mb_strlen') ? mb_strlen($block,'8bit') : strlen($block)); + } return substr($data,0,-2); case '*': // Multi-bulk replies $count=(int)$line; diff --git a/framework/cli/commands/MigrateCommand.php b/framework/cli/commands/MigrateCommand.php index 4da990b95a..6c1637e3ec 100644 --- a/framework/cli/commands/MigrateCommand.php +++ b/framework/cli/commands/MigrateCommand.php @@ -39,7 +39,7 @@ class MigrateCommand extends CConsoleCommand /** * @var string the name of the table for keeping applied migration information. * This table will be automatically created if not exists. Defaults to 'tbl_migration'. - * The table structure is: (version varchar(255) primary key, apply_time integer) + * The table structure is: (version varchar(180) primary key, apply_time integer) */ public $migrationTable='tbl_migration'; /** @@ -466,7 +466,7 @@ protected function createMigrationHistoryTable() $db=$this->getDbConnection(); echo 'Creating migration history table "'.$this->migrationTable.'"...'; $db->createCommand()->createTable($this->migrationTable,array( - 'version'=>'string NOT NULL PRIMARY KEY', + 'version'=>'varchar(180) NOT NULL PRIMARY KEY', 'apply_time'=>'integer', )); $db->createCommand()->insert($this->migrationTable,array( diff --git a/framework/cli/commands/ShellCommand.php b/framework/cli/commands/ShellCommand.php index 3aed3f1057..970b39baca 100644 --- a/framework/cli/commands/ShellCommand.php +++ b/framework/cli/commands/ShellCommand.php @@ -104,11 +104,8 @@ protected function runShell() // disable E_NOTICE so that the shell is more friendly error_reporting(E_ALL ^ E_NOTICE); - $_runner_=new CConsoleCommandRunner; - $_runner_->addCommands(dirname(__FILE__).'/shell'); - $_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell')); - if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false) - $_runner_->addCommands($_path_); + $_runner_=$this->createCommandRunner(); + $this->addCommands($_runner_); $_commands_=$_runner_->commands; $log=Yii::app()->log; @@ -139,6 +136,29 @@ protected function runShell() } } } + + /** + * Creates a commands runner + * @return CConsoleCommandRunner + * @since 1.1.15 + */ + protected function createCommandRunner() + { + return new CConsoleCommandRunner; + } + + /** + * Adds commands to runner + * @param CConsoleCommandRunner $runner + * @since 1.1.15 + */ + protected function addCommands(CConsoleCommandRunner $runner) + { + $runner->addCommands(Yii::getPathOfAlias('system.cli.commands.shell')); + $runner->addCommands(Yii::getPathOfAlias('application.commands.shell')); + if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false) + $runner->addCommands($_path_); + } } class ShellException extends CException diff --git a/framework/cli/views/webapp/protected/controllers/SiteController.php b/framework/cli/views/webapp/protected/controllers/SiteController.php index 1f28580422..a6a790e22c 100644 --- a/framework/cli/views/webapp/protected/controllers/SiteController.php +++ b/framework/cli/views/webapp/protected/controllers/SiteController.php @@ -62,7 +62,7 @@ public function actionContact() $headers="From: $name <{$model->email}>\r\n". "Reply-To: {$model->email}\r\n". "MIME-Version: 1.0\r\n". - "Content-type: text/plain; charset=UTF-8"; + "Content-Type: text/plain; charset=UTF-8"; mail(Yii::app()->params['adminEmail'],$subject,$model->body,$headers); Yii::app()->user->setFlash('contact','Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/framework/console/CConsoleCommand.php b/framework/console/CConsoleCommand.php index 0537a44827..34e29452e6 100644 --- a/framework/console/CConsoleCommand.php +++ b/framework/console/CConsoleCommand.php @@ -274,9 +274,9 @@ public function getHelp() $help='Usage: '.$this->getCommandRunner()->getScriptName().' '.$this->getName(); $options=$this->getOptionHelp(); if(empty($options)) - return $help; + return $help."\n"; if(count($options)===1) - return $help.' '.$options[0]; + return $help.' '.$options[0]."\n"; $help.=" \nActions:\n"; foreach($options as $option) $help.=' '.$option."\n"; diff --git a/framework/db/CDbCommand.php b/framework/db/CDbCommand.php index 04d2c19cd5..0ee36a20ce 100644 --- a/framework/db/CDbCommand.php +++ b/framework/db/CDbCommand.php @@ -122,7 +122,7 @@ public function __sleep() /** * Set the default fetch mode for this statement * @param mixed $mode fetch mode - * @return CDbCommand + * @return static * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php * @since 1.1.7 */ @@ -138,7 +138,7 @@ public function setFetchMode($mode) * This method is mainly used when a command object is being reused * multiple times for building different queries. * Calling this method will clean up all internal states of the command object. - * @return CDbCommand this command instance + * @return static this command instance * @since 1.1.6 */ public function reset() @@ -165,7 +165,7 @@ public function getText() * Specifies the SQL statement to be executed. * Any previous execution will be terminated or cancel. * @param string $value the SQL statement to be executed - * @return CDbCommand this command instance + * @return static this command instance */ public function setText($value) { @@ -239,7 +239,7 @@ public function cancel() * @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 (this is available since version 1.1.6) - * @return CDbCommand 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) @@ -265,7 +265,7 @@ public function bindParam($name, &$value, $dataType=null, $length=null, $driverO * 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 CDbCommand 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) @@ -286,7 +286,7 @@ public function bindValue($name, $value, $dataType=null) * @param array $values the values to be bound. This must be given in terms of an associative * array with array keys being the parameter names, and array values the corresponding parameter values. * For example, array(':name'=>'John', ':age'=>25). - * @return CDbCommand the current command being executed + * @return static the current command being executed * @since 1.1.5 */ public function bindValues($values) @@ -489,7 +489,7 @@ private function queryInternal($method,$mode,$params=array()) && ($cache=Yii::app()->getComponent($this->_connection->queryCacheID))!==null) { $this->_connection->queryCachingCount--; - $cacheKey='yii:dbquery'.$this->_connection->connectionString.':'.$this->_connection->username; + $cacheKey='yii:dbquery'.':'.$method.':'.$this->_connection->connectionString.':'.$this->_connection->username; $cacheKey.=':'.$this->getText().':'.serialize(array_merge($this->_paramLog,$params)); if(($result=$cache->get($cacheKey))!==false) { @@ -562,8 +562,6 @@ public function buildQuery($query) if(!empty($query['from'])) $sql.="\nFROM ".$query['from']; - else - throw new CDbException(Yii::t('yii','The DB query must contain the "from" portion.')); if(!empty($query['join'])) $sql.="\n".(is_array($query['join']) ? implode("\n",$query['join']) : $query['join']); @@ -600,7 +598,7 @@ public function buildQuery($query) * (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. This parameter is supported since version 1.1.8. - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function select($columns='*', $option='') @@ -692,7 +690,7 @@ public function setDistinct($value) * 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 CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function from($tables) @@ -774,7 +772,7 @@ public function setFrom($value) * * @param mixed $conditions the conditions that should be put in the WHERE part. * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function where($conditions, $params=array()) @@ -795,7 +793,7 @@ public function where($conditions, $params=array()) * * @param mixed $conditions the conditions that should be appended to the WHERE part. * @param array $params the parameters (name=>value) to be bound to the query. - * @return CDbCommand the command object itself. + * @return static the command object itself. * @since 1.1.13 */ public function andWhere($conditions,$params=array()) @@ -819,7 +817,7 @@ public function andWhere($conditions,$params=array()) * * @param mixed $conditions the conditions that should be appended to the WHERE part. * @param array $params the parameters (name=>value) to be bound to the query. - * @return CDbCommand the command object itself. + * @return static the command object itself. * @since 1.1.13 */ public function orWhere($conditions,$params=array()) @@ -875,7 +873,7 @@ public function join($table, $conditions, $params=array()) /** * Returns the join part in the query. * @return mixed the join part in the query. This can be an array representing - * multiple join fragments, or a string representing a single jojin fragment. + * multiple join fragments, or a string representing a single join fragment. * Each join fragment will contain the proper join operator (e.g. LEFT JOIN). * @since 1.1.6 */ @@ -960,13 +958,43 @@ public function naturalJoin($table) return $this->joinInternal('natural join', $table); } + /** + * Appends a NATURAL LEFT JOIN part to the query. + * Note that not all DBMS support NATURAL LEFT JOIN. + * @param string $table the table to be joined. + * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @return CDbCommand the command object itself + * @since 1.1.15 + */ + public function naturalLeftJoin($table) + { + return $this->joinInternal('natural left join', $table); + } + + /** + * Appends a NATURAL RIGHT JOIN part to the query. + * Note that not all DBMS support NATURAL RIGHT JOIN. + * @param string $table the table to be joined. + * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @return CDbCommand the command object itself + * @since 1.1.15 + */ + public function naturalRightJoin($table) + { + return $this->joinInternal('natural right join', $table); + } + /** * Sets the GROUP BY part of the query. * @param mixed $columns the columns to be grouped by. * 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 CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function group($columns) @@ -1015,7 +1043,7 @@ public function setGroup($value) * @param mixed $conditions the conditions to be put after HAVING. * Please refer to {@link where} on how to specify conditions. * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function having($conditions, $params=array()) @@ -1060,7 +1088,7 @@ public function setHaving($value) * $criteria->order('(1)'); *
    * - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function order($columns) @@ -1113,7 +1141,7 @@ public function setOrder($value) * Sets the LIMIT part of the query. * @param integer $limit the limit * @param integer $offset the offset - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function limit($limit, $offset=null) @@ -1148,7 +1176,7 @@ public function setLimit($value) /** * Sets the OFFSET part of the query. * @param integer $offset the offset - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function offset($offset) @@ -1181,7 +1209,7 @@ public function setOffset($value) /** * Appends a SQL statement using UNION operator. * @param string $sql the SQL statement to be appended using UNION - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ public function union($sql) @@ -1422,9 +1450,9 @@ public function alterColumn($table, $column, $type) * The method will properly quote the table and column names. * @param string $name the name of the foreign key constraint. * @param string $table the table that the foreign key constraint will be added to. - * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. + * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or pass as an array of column names. * @param string $refTable the table that the foreign key references to. - * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. + * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or pass as an array of column names. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL * @return integer number of rows affected by the execution. @@ -1451,15 +1479,15 @@ public function dropForeignKey($name, $table) * Builds and executes a SQL statement for creating a new index. * @param string $name the name of the index. The name will be properly quoted by the method. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. - * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas. The column names will be properly quoted by the method. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas or pass as an array of column names. Each column name will be properly quoted by the method, unless a parenthesis is found in the name. * @param boolean $unique whether to add UNIQUE constraint on the created index. * @return integer number of rows affected by the execution. * @since 1.1.6 */ - public function createIndex($name, $table, $column, $unique=false) + public function createIndex($name, $table, $columns, $unique=false) { - return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $column, $unique))->execute(); + return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $columns, $unique))->execute(); } /** @@ -1556,7 +1584,7 @@ private function processConditions($conditions) * @param mixed $conditions the join condition that should appear in the ON part. * Please refer to {@link where} on how to specify conditions. * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself + * @return static the command object itself * @since 1.1.6 */ private function joinInternal($type, $table, $conditions='', $params=array()) @@ -1587,7 +1615,8 @@ private function joinInternal($type, $table, $conditions='', $params=array()) * Builds a SQL statement for creating a primary key constraint. * @param string $name the name of the primary key constraint to be created. The name will be properly quoted by the method. * @param string $table the table who will be inheriting the primary key. The name will be properly quoted by the method. - * @param string $columns the column/s where the primary key will be effected. The name will be properly quoted by the method. + * @param string|array $columns comma separated string or array of columns that the primary key will consist of. + * Array value can be passed since 1.1.14. * @return integer number of rows affected by the execution. * @since 1.1.13 */ diff --git a/framework/db/CDbConnection.php b/framework/db/CDbConnection.php index 4390ca75aa..14ebfe8aae 100644 --- a/framework/db/CDbConnection.php +++ b/framework/db/CDbConnection.php @@ -185,7 +185,7 @@ class CDbConnection extends CApplicationComponent public $autoConnect=true; /** * @var string the charset used for database connection. The property is only used - * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset + * for MySQL, MariaDB and PostgreSQL databases. Defaults to null, meaning using default charset * as specified by the database. * * Note that if you're using GBK or BIG5 then it's highly recommended to @@ -235,7 +235,7 @@ class CDbConnection extends CApplicationComponent public $driverMap=array( 'pgsql'=>'CPgsqlSchema', // PostgreSQL 'mysqli'=>'CMysqlSchema', // MySQL - 'mysql'=>'CMysqlSchema', // MySQL + 'mysql'=>'CMysqlSchema', // MySQL,MariaDB 'sqlite'=>'CSqliteSchema', // sqlite 3 'sqlite2'=>'CSqliteSchema', // sqlite 2 'mssql'=>'CMssqlSchema', // Mssql driver on windows hosts @@ -347,7 +347,7 @@ public function setActive($value) * the query results into cache. * @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1, * meaning that the next SQL query will be cached. - * @return CDbConnection the connection instance itself. + * @return static the connection instance itself. * @since 1.1.7 */ public function cache($duration, $dependency=null, $queryCount=1) @@ -437,7 +437,7 @@ protected function createPdoInstance() /** * Initializes the open db connection. * This method is invoked right after the db connection is established. - * The default implementation is to set the charset for MySQL and PostgreSQL database connections. + * The default implementation is to set the charset for MySQL, MariaDB and PostgreSQL database connections. * @param PDO $pdo the PDO instance */ protected function initConnection($pdo) diff --git a/framework/db/CDbMigration.php b/framework/db/CDbMigration.php index 9849060b8b..baaef75f60 100644 --- a/framework/db/CDbMigration.php +++ b/framework/db/CDbMigration.php @@ -179,6 +179,23 @@ public function insert($table, $columns) echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n"; } + /** + * Creates and executes an INSERT SQL statement with multiple data. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $data an array of various column data (name=>value) to be inserted into the table. + * @since 1.1.15 + */ + public function insertMultiple($table, $data) + { + echo " > insert into $table ..."; + $time=microtime(true); + $builder=$this->getDbConnection()->getSchema()->getCommandBuilder(); + $command=$builder->createMultipleInsertCommand($table,$data); + $command->execute(); + echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n"; + } + /** * Creates and executes an UPDATE SQL statement. * The method will properly escape the column names and bind the values to be updated. @@ -335,15 +352,16 @@ public function alterColumn($table, $column, $type) * The method will properly quote the table and column names. * @param string $name the name of the foreign key constraint. * @param string $table the table that the foreign key constraint will be added to. - * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. + * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or pass as an array of column names. * @param string $refTable the table that the foreign key references to. - * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. + * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or pass as an array of column names. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL */ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete=null, $update=null) { - echo " > add foreign key $name: $table ($columns) references $refTable ($refColumns) ..."; + echo " > add foreign key $name: $table (".(is_array($columns) ? implode(',', $columns) : $columns). + ") references $refTable (".(is_array($refColumns) ? implode(',', $refColumns) : $refColumns).") ..."; $time=microtime(true); $this->getDbConnection()->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update); echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n"; @@ -366,15 +384,15 @@ public function dropForeignKey($name, $table) * Builds and executes a SQL statement for creating a new index. * @param string $name the name of the index. The name will be properly quoted by the method. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. - * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas. The column names will be properly quoted by the method. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas or pass as an array of column names. Each column name will be properly quoted by the method, unless a parenthesis is found in the name. * @param boolean $unique whether to add UNIQUE constraint on the created index. */ - public function createIndex($name, $table, $column, $unique=false) + public function createIndex($name, $table, $columns, $unique=false) { - echo " > create".($unique ? ' unique':'')." index $name on $table ($column) ..."; + echo " > create".($unique ? ' unique':'')." index $name on $table (".(is_array($columns) ? implode(',', $columns) : $columns).") ..."; $time=microtime(true); - $this->getDbConnection()->createCommand()->createIndex($name, $table, $column, $unique); + $this->getDbConnection()->createCommand()->createIndex($name, $table, $columns, $unique); echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n"; } @@ -408,12 +426,13 @@ public function refreshTableSchema($table) * Builds and executes a SQL statement for creating a primary key, supports composite primary keys. * @param string $name name of the primary key constraint to add * @param string $table name of the table to add primary key to - * @param string $columns name of the column to utilise as primary key. If there are multiple columns, separate them with commas. + * @param string|array $columns comma separated string or array of columns that the primary key will consist of. + * Array value can be passed since 1.1.14. * @since 1.1.13 */ public function addPrimaryKey($name,$table,$columns) { - echo " > alter table $table add constraint $name primary key ($columns) ..."; + echo " > alter table $table add constraint $name primary key (".(is_array($columns) ? implode(',', $columns) : $columns).") ..."; $time=microtime(true); $this->getDbConnection()->createCommand()->addPrimaryKey($name,$table,$columns); echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n"; diff --git a/framework/db/ar/CActiveFinder.php b/framework/db/ar/CActiveFinder.php index affdbb4330..58a39369dd 100644 --- a/framework/db/ar/CActiveFinder.php +++ b/framework/db/ar/CActiveFinder.php @@ -246,6 +246,9 @@ private function buildJoinTree($parent,$with,$options=null) if(!empty($options['scopes'])) $scopes=array_merge($scopes,(array)$options['scopes']); // no need for complex merging + if(!empty($options['joinOptions'])) + $relation->joinOptions=$options['joinOptions']; + $model->resetScope(false); $criteria=$model->getDbCriteria(); $criteria->scopes=$scopes; @@ -482,7 +485,6 @@ public function lazyFind($baseRecord) $query=new CJoinQuery($child); $query->selects=array($child->getColumnSelect($child->relation->select)); $query->conditions=array( - $child->relation->condition, $child->relation->on, ); $query->groups[]=$child->relation->group; @@ -537,6 +539,9 @@ private function applyLazyCondition($query,$record) $parent=$this->_parent; if($this->relation instanceof CManyManyRelation) { + $query->conditions=array( + $this->relation->condition, + ); $joinTableName=$this->relation->getJunctionTableName(); if(($joinTable=$schema->getTable($joinTableName))===null) throw new CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.', @@ -742,7 +747,7 @@ public function count($criteria=null) else { $select=is_array($criteria->select) ? implode(',',$criteria->select) : $criteria->select; - if($select!=='*' && !strncasecmp($select,'count',5)) + if($select!=='*' && preg_match('/^count\s*\(/',trim($select))) $query->selects=array($select); elseif(is_string($this->_table->primaryKey)) { @@ -965,9 +970,9 @@ public function getColumnSelect($select='*') $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias); elseif(is_array($this->_pkAlias)) { - foreach($this->_table->primaryKey as $name) - if(!isset($selected[$name])) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_pkAlias[$name]); + foreach($this->_pkAlias as $name=>$alias) + if(!isset($selected[$alias])) + $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias); } } @@ -1113,7 +1118,12 @@ private function joinOneMany($fke,$fks,$pke,$parent) } if(!empty($this->relation->on)) $joins[]=$this->relation->on; - return $this->relation->joinType . ' ' . $this->getTableNameWithAlias() . ' ON (' . implode(') AND (',$joins).')'; + + if(!empty($this->relation->joinOptions) && is_string($this->relation->joinOptions)) + return $this->relation->joinType.' '.$this->getTableNameWithAlias().' '.$this->relation->joinOptions. + ' ON ('.implode(') AND (',$joins).')'; + else + return $this->relation->joinType.' '.$this->getTableNameWithAlias().' ON ('.implode(') AND (',$joins).')'; } /** @@ -1181,8 +1191,20 @@ private function joinManyMany($joinTable,$fks,$parent) if($parentCondition!==array() && $childCondition!==array()) { $join=$this->relation->joinType.' '.$joinTable->rawName.' '.$joinAlias; + + if(is_array($this->relation->joinOptions) && isset($this->relation->joinOptions[0]) && + is_string($this->relation->joinOptions[0])) + $join.=' '.$this->relation->joinOptions[0]; + elseif(!empty($this->relation->joinOptions) && is_string($this->relation->joinOptions)) + $join.=' '.$this->relation->joinOptions; + $join.=' ON ('.implode(') AND (',$parentCondition).')'; $join.=' '.$this->relation->joinType.' '.$this->getTableNameWithAlias(); + + if(is_array($this->relation->joinOptions) && isset($this->relation->joinOptions[1]) && + is_string($this->relation->joinOptions[1])) + $join.=' '.$this->relation->joinOptions[1]; + $join.=' ON ('.implode(') AND (',$childCondition).')'; if(!empty($this->relation->on)) $join.=' AND ('.$this->relation->on.')'; @@ -1315,7 +1337,7 @@ public function join($element) public function createCommand($builder) { $sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects); - $sql.=' FROM ' . implode(' ',$this->joins); + $sql.=' FROM ' . implode(' ',array_unique($this->joins)); $conditions=array(); foreach($this->conditions as $condition) @@ -1501,9 +1523,10 @@ private function queryOneMany() $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false); } - /* + /** * @param string $joinTableName jointablename * @param string $keys keys + * @throws CDbException */ private function queryManyMany($joinTableName,$keys) { @@ -1517,7 +1540,7 @@ private function queryManyMany($joinTableName,$keys) $tableAlias=$model->getTableAlias(true); if(($joinTable=$builder->getSchema()->getTable($joinTableName))===null) - throw new CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.', + throw new CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.', array('{class}'=>get_class($this->_parent->model), '{relation}'=>$relation->name, '{joinTable}'=>$joinTableName))); $fks=preg_split('/\s*,\s*/',$keys,-1,PREG_SPLIT_NO_EMPTY); diff --git a/framework/db/ar/CActiveRecord.php b/framework/db/ar/CActiveRecord.php index c40c5b80d7..762bd66569 100644 --- a/framework/db/ar/CActiveRecord.php +++ b/framework/db/ar/CActiveRecord.php @@ -105,7 +105,7 @@ public function init() * the query results into cache. * @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1, * meaning that the next SQL query will be cached. - * @return CActiveRecord the active record instance itself. + * @return static the active record instance itself. * @since 1.1.7 */ public function cache($duration, $dependency=null, $queryCount=1) @@ -357,7 +357,7 @@ public function defaultScope() * Resets all scopes and criterias applied. * * @param boolean $resetDefault including default scope. This parameter available since 1.1.12 - * @return CActiveRecord + * @return static the AR instance itself * @since 1.1.2 */ public function resetScope($resetDefault=true) @@ -384,7 +384,7 @@ public function resetScope($resetDefault=true) * * * @param string $className active record class name. - * @return CActiveRecord active record model instance. + * @return static active record model instance. */ public static function model($className=__CLASS__) { @@ -435,7 +435,10 @@ public function refreshMetaData() */ public function tableName() { - return get_class($this); + $tableName = get_class($this); + if(($pos=strrpos($tableName,'\\')) !== false) + return substr($tableName,$pos+1); + return $tableName; } /** @@ -1072,7 +1075,7 @@ public function insert($attributes=null) { Yii::trace(get_class($this).'.insert()','system.db.ar.CActiveRecord'); $builder=$this->getCommandBuilder(); - $table=$this->getMetaData()->tableSchema; + $table=$this->getTableSchema(); $command=$builder->createInsertCommand($table,$this->getAttributes($attributes)); if($command->execute()) { @@ -1272,7 +1275,7 @@ public function equals($record) */ public function getPrimaryKey() { - $table=$this->getMetaData()->tableSchema; + $table=$this->getTableSchema(); if(is_string($table->primaryKey)) return $this->{$table->primaryKey}; elseif(is_array($table->primaryKey)) @@ -1296,7 +1299,7 @@ public function getPrimaryKey() public function setPrimaryKey($value) { $this->_pk=$this->getPrimaryKey(); - $table=$this->getMetaData()->tableSchema; + $table=$this->getTableSchema(); if(is_string($table->primaryKey)) $this->{$table->primaryKey}=$value; elseif(is_array($table->primaryKey)) @@ -1347,7 +1350,7 @@ protected function query($criteria,$all=false) { if(!$all) $criteria->limit=1; - $command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria,$this->getTableAlias()); + $command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria); return $all ? $this->populateRecords($command->queryAll(), true, $criteria->index) : $this->populateRecord($command->queryRow()); } else @@ -1447,7 +1450,7 @@ public function setTableAlias($alias) * @param array $params parameters to be bound to an SQL statement. * This is only used when the first parameter is a string (query condition). * In other cases, please use {@link CDbCriteria::params} to set parameters. - * @return CActiveRecord the record found. Null if no record is found. + * @return static the record found. Null if no record is found. */ public function find($condition='',$params=array()) { @@ -1461,7 +1464,7 @@ public function find($condition='',$params=array()) * See {@link find()} for detailed explanation about $condition and $params. * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord[] list of active records satisfying the specified condition. An empty array is returned if none is found. + * @return static[] list of active records satisfying the specified condition. An empty array is returned if none is found. */ public function findAll($condition='',$params=array()) { @@ -1476,7 +1479,7 @@ public function findAll($condition='',$params=array()) * @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord the record found. Null if none is found. + * @return static the record found. Null if none is found. */ public function findByPk($pk,$condition='',$params=array()) { @@ -1492,7 +1495,7 @@ public function findByPk($pk,$condition='',$params=array()) * @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord[] the records found. An empty array is returned if none is found. + * @return static[] the records found. An empty array is returned if none is found. */ public function findAllByPk($pk,$condition='',$params=array()) { @@ -1509,7 +1512,7 @@ public function findAllByPk($pk,$condition='',$params=array()) * An attribute value can be an array which will be used to generate an IN condition. * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord the record found. Null if none is found. + * @return static the record found. Null if none is found. */ public function findByAttributes($attributes,$condition='',$params=array()) { @@ -1526,7 +1529,7 @@ public function findByAttributes($attributes,$condition='',$params=array()) * An attribute value can be an array which will be used to generate an IN condition. * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord[] the records found. An empty array is returned if none is found. + * @return static[] the records found. An empty array is returned if none is found. */ public function findAllByAttributes($attributes,$condition='',$params=array()) { @@ -1540,7 +1543,7 @@ public function findAllByAttributes($attributes,$condition='',$params=array()) * Finds a single active record with the specified SQL statement. * @param string $sql the SQL statement * @param array $params parameters to be bound to the SQL statement - * @return CActiveRecord the record found. Null if none is found. + * @return static the record found. Null if none is found. */ public function findBySql($sql,$params=array()) { @@ -1563,7 +1566,7 @@ public function findBySql($sql,$params=array()) * Finds all active records using the specified SQL statement. * @param string $sql the SQL statement * @param array $params parameters to be bound to the SQL statement - * @return CActiveRecord[] the records found. An empty array is returned if none is found. + * @return static[] the records found. An empty array is returned if none is found. */ public function findAllBySql($sql,$params=array()) { @@ -1592,8 +1595,8 @@ public function findAllBySql($sql,$params=array()) public function count($condition='',$params=array()) { Yii::trace(get_class($this).'.count()','system.db.ar.CActiveRecord'); - $builder=$this->getCommandBuilder(); $this->beforeCount(); + $builder=$this->getCommandBuilder(); $criteria=$builder->createCriteria($condition,$params); $this->applyScopes($criteria); @@ -1700,7 +1703,7 @@ public function exists($condition='',$params=array()) * ))->findAll(); * * - * @return CActiveRecord the AR object itself. + * @return static the AR object itself. */ public function with() { @@ -1719,7 +1722,7 @@ public function with() * Sets {@link CDbCriteria::together} property to be true. * This is only used in relational AR query. Please refer to {@link CDbCriteria::together} * for more details. - * @return CActiveRecord the AR object itself + * @return static the AR object itself * @since 1.1.4 */ public function together() @@ -1842,7 +1845,7 @@ public function deleteAllByAttributes($attributes,$condition='',$params=array()) * This method is internally used by the find methods. * @param array $attributes attribute values (column name=>column value) * @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated. - * @return CActiveRecord the newly created active record. The class of the object is the same as the model class. + * @return static the newly created active record. The class of the object is the same as the model class. * Null is returned if the input data is false. */ public function populateRecord($attributes,$callAfterFind=true) @@ -1877,7 +1880,7 @@ public function populateRecord($attributes,$callAfterFind=true) * @param boolean $callAfterFind whether to call {@link afterFind} after each record is populated. * @param string $index the name of the attribute whose value will be used as indexes of the query result array. * If null, it means the array will be indexed by zero-based integers. - * @return CActiveRecord[] list of active records. + * @return static[] list of active records. */ public function populateRecords($data,$callAfterFind=true,$index=null) { @@ -1903,7 +1906,7 @@ public function populateRecords($data,$callAfterFind=true,$index=null) * For example, by creating a record based on the value of a column, * you may implement the so-called single-table inheritance mapping. * @param array $attributes list of attribute values for the active records. - * @return CActiveRecord the active record + * @return static the active record */ protected function instantiate($attributes) { @@ -1971,6 +1974,14 @@ class CBaseActiveRelation extends CComponent * @since 1.1.3 */ public $join=''; + /** + * @var string|array property for setting post-JOIN operations such as USE INDEX. + * String typed value can be used with JOINs for HAS_MANY and MANY_MANY relations, while array typed + * value designed to be used only with MANY_MANY relations. First array element will be used for junction + * table JOIN and second array element will be used for target table JOIN. + * @since 1.1.15 + */ + public $joinOptions=''; /** * @var string HAVING clause. For {@link CActiveRelation} descendant classes, column names * referenced in this property should be disambiguated with prefix 'relationName.'. @@ -2082,6 +2093,16 @@ class CStatRelation extends CBaseActiveRelation * receive a statistical query result. Defaults to 0. */ public $defaultValue=0; + /** + * @var mixed scopes to apply + * Can be set to the one of the following: + *
      + *
    • Single scope: 'scopes'=>'scopeName'.
    • + *
    • Multiple scopes: 'scopes'=>array('scopeName1','scopeName2').
    • + *
    + * @since 1.1.15 + */ + public $scopes; /** * Merges this relation with a criteria specified dynamically. diff --git a/framework/db/ar/CActiveRecordBehavior.php b/framework/db/ar/CActiveRecordBehavior.php index 373d619032..443d15ba81 100644 --- a/framework/db/ar/CActiveRecordBehavior.php +++ b/framework/db/ar/CActiveRecordBehavior.php @@ -54,7 +54,7 @@ protected function beforeSave($event) * Responds to {@link CActiveRecord::onAfterSave} event. * Override this method and make it public if you want to handle the corresponding event * of the {@link CBehavior::owner owner}. - * @param CModelEvent $event event parameter + * @param CEvent $event event parameter */ protected function afterSave($event) { diff --git a/framework/db/schema/CDbCommandBuilder.php b/framework/db/schema/CDbCommandBuilder.php index 603b912996..72fda3e0de 100644 --- a/framework/db/schema/CDbCommandBuilder.php +++ b/framework/db/schema/CDbCommandBuilder.php @@ -288,7 +288,7 @@ protected function composeMultipleInsertCommand($table,array $data,array $templa $templates ); $this->ensureTable($table); - $tableName=$this->getDbConnection()->quoteTableName($table->name); + $tableName=$table->rawName; $params=array(); $columnInsertNames=array(); $rowInsertValues=array(); @@ -499,7 +499,7 @@ public function applyOrder($sql,$orderBy) /** * Alters the SQL to apply LIMIT and OFFSET. - * Default implementation is applicable for PostgreSQL, MySQL and SQLite. + * Default implementation is applicable for PostgreSQL, MySQL, MariaDB and SQLite. * @param string $sql SQL query string without LIMIT and OFFSET. * @param integer $limit maximum number of rows, -1 to ignore limit. * @param integer $offset row offset, -1 to ignore offset. @@ -736,7 +736,7 @@ public function createSearchCondition($table,$columns,$keywords,$prefix=null,$ca $condition=array(); foreach($keywords as $keyword) { - $keyword='%'.strtr($keyword,array('%'=>'\%', '_'=>'\_')).'%'; + $keyword='%'.strtr($keyword,array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')).'%'; if($caseSensitive) $condition[]=$prefix.$column->rawName.' LIKE '.$this->_connection->quoteValue('%'.$keyword.'%'); else diff --git a/framework/db/schema/CDbCriteria.php b/framework/db/schema/CDbCriteria.php index 95061c483d..86fb1b6085 100644 --- a/framework/db/schema/CDbCriteria.php +++ b/framework/db/schema/CDbCriteria.php @@ -213,7 +213,7 @@ public function __wakeup() * After calling this method, the {@link condition} property will be modified. * @param mixed $condition the new condition. It can be either a string or an array of strings. * @param string $operator the operator to join different conditions. Defaults to 'AND'. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself */ public function addCondition($condition,$operator='AND') { @@ -246,7 +246,7 @@ public function addCondition($condition,$operator='AND') * @param string $operator the operator used to concatenate the new condition with the existing one. * Defaults to 'AND'. * @param string $like the LIKE operator. Defaults to 'LIKE'. You may also set this to be 'NOT LIKE'. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself */ public function addSearchCondition($column,$keyword,$escape=true,$operator='AND',$like='LIKE') { @@ -269,7 +269,7 @@ public function addSearchCondition($column,$keyword,$escape=true,$operator='AND' * @param array $values list of values that the column value should be in * @param string $operator the operator used to concatenate the new condition with the existing one. * Defaults to 'AND'. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself */ public function addInCondition($column,$values,$operator='AND') { @@ -309,7 +309,7 @@ public function addInCondition($column,$values,$operator='AND') * @param array $values list of values that the column value should not be in * @param string $operator the operator used to concatenate the new condition with the existing one. * Defaults to 'AND'. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself * @since 1.1.1 */ public function addNotInCondition($column,$values,$operator='AND') @@ -349,7 +349,7 @@ public function addNotInCondition($column,$values,$operator='AND') * @param string $columnOperator the operator to concatenate multiple column matching condition. Defaults to 'AND'. * @param string $operator the operator used to concatenate the new condition with the existing one. * Defaults to 'AND'. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself */ public function addColumnCondition($columns,$columnOperator='AND',$operator='AND') { @@ -408,7 +408,7 @@ public function addColumnCondition($columns,$columnOperator='AND',$operator='AND * and _ (matches a single character) will be escaped, and the value will be surrounded with a % * character on both ends. When this parameter is false, the value will be directly used for * matching without any change. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself * @since 1.1.1 */ public function compare($column, $value, $partialMatch=false, $operator='AND', $escape=true) @@ -462,7 +462,7 @@ public function compare($column, $value, $partialMatch=false, $operator='AND', $ * @param string $valueEnd the ending value to end the between search. * @param string $operator the operator used to concatenate the new condition with the existing one. * Defaults to 'AND'. - * @return CDbCriteria the criteria object itself + * @return static the criteria object itself * @since 1.1.2 */ public function addBetweenCondition($column,$valueStart,$valueEnd,$operator='AND') @@ -520,7 +520,7 @@ public function mergeWith($criteria,$operator='AND') if($this->params!==$criteria->params) $this->params=array_merge($this->params,$criteria->params); - if($criteria->limit>0) + if($criteria->limit>=0) $this->limit=$criteria->limit; if($criteria->offset>=0) diff --git a/framework/db/schema/CDbSchema.php b/framework/db/schema/CDbSchema.php index 8ea61ca960..18d3f0e932 100644 --- a/framework/db/schema/CDbSchema.php +++ b/framework/db/schema/CDbSchema.php @@ -316,10 +316,11 @@ protected function findTableNames($schema='') * These abstract column types are supported (using MySQL as example to explain the corresponding * physical types): *
      - *
    • pk: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
    • + *
    • pk and bigpk: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" or "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
    • *
    • string: string type, will be converted into "varchar(255)"
    • *
    • text: a long string type, will be converted into "text"
    • *
    • integer: integer type, will be converted into "int(11)"
    • + *
    • bigint: integer type, will be converted into "bigint(20)"
    • *
    • boolean: boolean type, will be converted into "tinyint(1)"
    • *
    • float: float number type, will be converted into "float"
    • *
    • decimal: decimal number type, will be converted into "decimal"
    • @@ -367,7 +368,7 @@ public function getColumnType($type) * @return string the SQL statement for creating a new DB table. * @since 1.1.6 */ - public function createTable($table, $columns, $options=null) + public function createTable($table,$columns,$options=null) { $cols=array(); foreach($columns as $name=>$type) @@ -388,7 +389,7 @@ public function createTable($table, $columns, $options=null) * @return string the SQL statement for renaming a DB table. * @since 1.1.6 */ - public function renameTable($table, $newName) + public function renameTable($table,$newName) { return 'RENAME TABLE ' . $this->quoteTableName($table) . ' TO ' . $this->quoteTableName($newName); } @@ -425,7 +426,7 @@ public function truncateTable($table) * @return string the SQL statement for adding a new column. * @since 1.1.6 */ - public function addColumn($table, $column, $type) + public function addColumn($table,$column,$type) { return 'ALTER TABLE ' . $this->quoteTableName($table) . ' ADD ' . $this->quoteColumnName($column) . ' ' @@ -439,7 +440,7 @@ public function addColumn($table, $column, $type) * @return string the SQL statement for dropping a DB column. * @since 1.1.6 */ - public function dropColumn($table, $column) + public function dropColumn($table,$column) { return "ALTER TABLE ".$this->quoteTableName($table) ." DROP COLUMN ".$this->quoteColumnName($column); @@ -453,7 +454,7 @@ public function dropColumn($table, $column) * @return string the SQL statement for renaming a DB column. * @since 1.1.6 */ - public function renameColumn($table, $name, $newName) + public function renameColumn($table,$name,$newName) { return "ALTER TABLE ".$this->quoteTableName($table) . " RENAME COLUMN ".$this->quoteColumnName($name) @@ -470,7 +471,7 @@ public function renameColumn($table, $name, $newName) * @return string the SQL statement for changing the definition of a column. * @since 1.1.6 */ - public function alterColumn($table, $column, $type) + public function alterColumn($table,$column,$type) { return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE ' . $this->quoteColumnName($column) . ' ' @@ -483,27 +484,29 @@ public function alterColumn($table, $column, $type) * The method will properly quote the table and column names. * @param string $name the name of the foreign key constraint. * @param string $table the table that the foreign key constraint will be added to. - * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. + * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or pass as an array of column names. * @param string $refTable the table that the foreign key references to. - * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. + * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or pass as an array of column names. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL * @return string the SQL statement for adding a foreign key constraint to an existing table. * @since 1.1.6 */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete=null, $update=null) + public function addForeignKey($name,$table,$columns,$refTable,$refColumns,$delete=null,$update=null) { - $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY); + if(is_string($columns)) + $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY); foreach($columns as $i=>$col) $columns[$i]=$this->quoteColumnName($col); - $refColumns=preg_split('/\s*,\s*/',$refColumns,-1,PREG_SPLIT_NO_EMPTY); + if(is_string($refColumns)) + $refColumns=preg_split('/\s*,\s*/',$refColumns,-1,PREG_SPLIT_NO_EMPTY); foreach($refColumns as $i=>$col) $refColumns[$i]=$this->quoteColumnName($col); $sql='ALTER TABLE '.$this->quoteTableName($table) .' ADD CONSTRAINT '.$this->quoteColumnName($name) - .' FOREIGN KEY ('.implode(', ', $columns).')' + .' FOREIGN KEY ('.implode(', ',$columns).')' .' REFERENCES '.$this->quoteTableName($refTable) - .' ('.implode(', ', $refColumns).')'; + .' ('.implode(', ',$refColumns).')'; if($delete!==null) $sql.=' ON DELETE '.$delete; if($update!==null) @@ -518,7 +521,7 @@ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $ * @return string the SQL statement for dropping a foreign key constraint. * @since 1.1.6 */ - public function dropForeignKey($name, $table) + public function dropForeignKey($name,$table) { return 'ALTER TABLE '.$this->quoteTableName($table) .' DROP CONSTRAINT '.$this->quoteColumnName($name); @@ -528,16 +531,17 @@ public function dropForeignKey($name, $table) * Builds a SQL statement for creating a new index. * @param string $name the name of the index. The name will be properly quoted by the method. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. - * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas. Each column name will be properly quoted by the method, unless a parenthesis is found in the name. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas or pass as an array of column names. Each column name will be properly quoted by the method, unless a parenthesis is found in the name. * @param boolean $unique whether to add UNIQUE constraint on the created index. * @return string the SQL statement for creating a new index. * @since 1.1.6 */ - public function createIndex($name, $table, $column, $unique=false) + public function createIndex($name,$table,$columns,$unique=false) { $cols=array(); - $columns=preg_split('/\s*,\s*/',$column,-1,PREG_SPLIT_NO_EMPTY); + if(is_string($columns)) + $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY); foreach($columns as $col) { if(strpos($col,'(')!==false) @@ -557,7 +561,7 @@ public function createIndex($name, $table, $column, $unique=false) * @return string the SQL statement for dropping an index. * @since 1.1.6 */ - public function dropIndex($name, $table) + public function dropIndex($name,$table) { return 'DROP INDEX '.$this->quoteTableName($name).' ON '.$this->quoteTableName($table); } @@ -579,7 +583,7 @@ public function addPrimaryKey($name,$table,$columns) $columns[$i]=$this->quoteColumnName($col); return 'ALTER TABLE ' . $this->quoteTableName($table) . ' ADD CONSTRAINT ' . $this->quoteColumnName($name) . ' PRIMARY KEY (' - . implode(', ', $columns). ' )'; + . implode(', ',$columns). ' )'; } /** diff --git a/framework/db/schema/mssql/CMssqlCommandBuilder.php b/framework/db/schema/mssql/CMssqlCommandBuilder.php index 56aedfd59d..aef2fd4f8b 100644 --- a/framework/db/schema/mssql/CMssqlCommandBuilder.php +++ b/framework/db/schema/mssql/CMssqlCommandBuilder.php @@ -203,9 +203,9 @@ protected function rewriteLimitOffsetSql($sql, $limit, $offset) $fetch = $limit+$offset; $sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $fetch", $sql); $ordering = $this->findOrdering($sql); - $orginalOrdering = $this->joinOrdering($ordering, '[__outer__]'); + $originalOrdering = $this->joinOrdering($ordering, '[__outer__]'); $reverseOrdering = $this->joinOrdering($this->reverseDirection($ordering), '[__inner__]'); - $sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner__] {$reverseOrdering}) as [__outer__] {$orginalOrdering}"; + $sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner__] {$reverseOrdering}) as [__outer__] {$originalOrdering}"; return $sql; } diff --git a/framework/db/schema/mssql/CMssqlSchema.php b/framework/db/schema/mssql/CMssqlSchema.php index 32f87e912d..da2983f39b 100644 --- a/framework/db/schema/mssql/CMssqlSchema.php +++ b/framework/db/schema/mssql/CMssqlSchema.php @@ -26,9 +26,11 @@ class CMssqlSchema extends CDbSchema */ public $columnTypes=array( 'pk' => 'int IDENTITY PRIMARY KEY', + 'bigpk' => 'bigint IDENTITY PRIMARY KEY', 'string' => 'varchar(255)', 'text' => 'text', 'integer' => 'int', + 'bigint' => 'bigint', 'float' => 'float', 'decimal' => 'decimal', 'datetime' => 'datetime', diff --git a/framework/db/schema/mysql/CMysqlCommandBuilder.php b/framework/db/schema/mysql/CMysqlCommandBuilder.php index 539a6486b0..5254075d99 100644 --- a/framework/db/schema/mysql/CMysqlCommandBuilder.php +++ b/framework/db/schema/mysql/CMysqlCommandBuilder.php @@ -40,4 +40,19 @@ public function applyJoin($sql,$join) else return $sql.' '.$join; } + + /** + * Alters the SQL to apply LIMIT and OFFSET. + * @param string $sql SQL query string without LIMIT and OFFSET. + * @param integer $limit maximum number of rows, -1 to ignore limit. + * @param integer $offset row offset, -1 to ignore offset. + * @return string SQL with LIMIT and OFFSET + */ + public function applyLimit($sql,$limit,$offset) + { + // Ugly, but this is how MySQL recommends doing it: https://dev.mysql.com/doc/refman/5.0/en/select.html + if($limit<=0 && $offset>0) + $limit=PHP_INT_MAX; + return parent::applyLimit($sql,$limit,$offset); + } } diff --git a/framework/db/schema/mysql/CMysqlSchema.php b/framework/db/schema/mysql/CMysqlSchema.php index eb1049d56d..3d4150e0fc 100644 --- a/framework/db/schema/mysql/CMysqlSchema.php +++ b/framework/db/schema/mysql/CMysqlSchema.php @@ -23,9 +23,11 @@ class CMysqlSchema extends CDbSchema */ public $columnTypes=array( 'pk' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + 'bigpk' => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', 'string' => 'varchar(255)', 'text' => 'text', 'integer' => 'int(11)', + 'bigint' => 'bigint(20)', 'float' => 'float', 'decimal' => 'decimal', 'datetime' => 'datetime', diff --git a/framework/db/schema/oci/COciCommandBuilder.php b/framework/db/schema/oci/COciCommandBuilder.php index 89013a7be0..0814431013 100644 --- a/framework/db/schema/oci/COciCommandBuilder.php +++ b/framework/db/schema/oci/COciCommandBuilder.php @@ -33,7 +33,6 @@ public function getLastInsertID($table) /** * Alters the SQL to apply LIMIT and OFFSET. - * Default implementation is applicable for PostgreSQL, MySQL and SQLite. * @param string $sql SQL query string without LIMIT and OFFSET. * @param integer $limit maximum number of rows, -1 to ignore limit. * @param integer $offset row offset, -1 to ignore offset. diff --git a/framework/db/schema/oci/COciSchema.php b/framework/db/schema/oci/COciSchema.php index 353bbcc9bb..ff7b7ad672 100644 --- a/framework/db/schema/oci/COciSchema.php +++ b/framework/db/schema/oci/COciSchema.php @@ -26,9 +26,11 @@ class COciSchema extends CDbSchema */ public $columnTypes=array( 'pk' => 'NUMBER(10) NOT NULL PRIMARY KEY', + 'bigpk' => 'NUMBER(20) NOT NULL PRIMARY KEY', 'string' => 'VARCHAR2(255)', 'text' => 'CLOB', 'integer' => 'NUMBER(10)', + 'bigint' => 'NUMBER(20)', 'float' => 'NUMBER', 'decimal' => 'NUMBER', 'datetime' => 'TIMESTAMP', diff --git a/framework/db/schema/pgsql/CPgsqlColumnSchema.php b/framework/db/schema/pgsql/CPgsqlColumnSchema.php index 50bd72817b..c0daf3c080 100644 --- a/framework/db/schema/pgsql/CPgsqlColumnSchema.php +++ b/framework/db/schema/pgsql/CPgsqlColumnSchema.php @@ -50,8 +50,8 @@ protected function extractDefault($defaultValue) $this->defaultValue=null; elseif(preg_match('/^\'(.*)\'::/',$defaultValue,$matches)) $this->defaultValue=$this->typecast(str_replace("''","'",$matches[1])); - elseif(preg_match('/^-?\d+(\.\d*)?$/',$defaultValue,$matches)) - $this->defaultValue=$this->typecast($defaultValue); + elseif(preg_match('/^(-?\d+(\.\d*)?)(::.*)?$/',$defaultValue,$matches)) + $this->defaultValue=$this->typecast($matches[1]); // else is null } } diff --git a/framework/db/schema/pgsql/CPgsqlSchema.php b/framework/db/schema/pgsql/CPgsqlSchema.php index f77373dfa1..0c874e6124 100644 --- a/framework/db/schema/pgsql/CPgsqlSchema.php +++ b/framework/db/schema/pgsql/CPgsqlSchema.php @@ -25,9 +25,11 @@ class CPgsqlSchema extends CDbSchema */ public $columnTypes=array( 'pk' => 'serial NOT NULL PRIMARY KEY', + 'bigpk' => 'bigserial NOT NULL PRIMARY KEY', 'string' => 'character varying (255)', 'text' => 'text', 'integer' => 'integer', + 'bigint' => 'bigint', 'float' => 'double precision', 'decimal' => 'numeric', 'datetime' => 'timestamp', @@ -391,7 +393,7 @@ public function addColumn($table, $column, $type) $type=$this->getColumnType($type); $sql='ALTER TABLE ' . $this->quoteTableName($table) . ' ADD COLUMN ' . $this->quoteColumnName($column) . ' ' - . $this->getColumnType($type); + . $type; return $sql; } @@ -413,6 +415,42 @@ public function alterColumn($table, $column, $type) return $sql; } + /** + * Builds a SQL statement for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas. Each column name will be properly quoted by the method, unless a parenthesis is found in the name. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + * @return string the SQL statement for creating a new index. + * @since 1.1.6 + */ + public function createIndex($name, $table, $columns, $unique=false) + { + $cols=array(); + if (is_string($columns)) + $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY); + foreach($columns as $col) + { + if(strpos($col,'(')!==false) + $cols[]=$col; + else + $cols[]=$this->quoteColumnName($col); + } + if ($unique) + { + return 'ALTER TABLE ONLY ' + . $this->quoteTableName($table).' ADD CONSTRAINT ' + . $this->quoteTableName($name).' UNIQUE ('.implode(', ',$cols).')'; + } + else + { + return 'CREATE INDEX ' + . $this->quoteTableName($name).' ON ' + . $this->quoteTableName($table).' ('.implode(', ',$cols).')'; + } + } + /** * Builds a SQL statement for dropping an index. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. diff --git a/framework/db/schema/sqlite/CSqliteSchema.php b/framework/db/schema/sqlite/CSqliteSchema.php index a2d263a714..0308a6fd6c 100644 --- a/framework/db/schema/sqlite/CSqliteSchema.php +++ b/framework/db/schema/sqlite/CSqliteSchema.php @@ -23,9 +23,11 @@ class CSqliteSchema extends CDbSchema */ public $columnTypes=array( 'pk' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + 'bigpk' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', 'string' => 'varchar(255)', 'text' => 'text', 'integer' => 'integer', + 'bigint' => 'integer', 'float' => 'float', 'decimal' => 'decimal', 'datetime' => 'datetime', diff --git a/framework/gii/components/Pear/Text/Diff.php b/framework/gii/components/Pear/Text/Diff.php index 205b8a7c88..4b30968f9c 100644 --- a/framework/gii/components/Pear/Text/Diff.php +++ b/framework/gii/components/Pear/Text/Diff.php @@ -294,7 +294,7 @@ class Text_MappedDiff extends Text_Diff { /** * Computes a diff between sequences of strings. * - * This can be used to compute things like case-insensitve diffs, or diffs + * This can be used to compute things like case-insensitive diffs, or diffs * which ignore changes in white-space. * * @param array $from_lines An array of strings. diff --git a/framework/gii/components/Pear/Text/Diff/Mapped.php b/framework/gii/components/Pear/Text/Diff/Mapped.php index 855b56ae8c..908e4de61c 100644 --- a/framework/gii/components/Pear/Text/Diff/Mapped.php +++ b/framework/gii/components/Pear/Text/Diff/Mapped.php @@ -15,7 +15,7 @@ class Text_Diff_Mapped extends Text_Diff { /** * Computes a diff between sequences of strings. * - * This can be used to compute things like case-insensitve diffs, or diffs + * This can be used to compute things like case-insensitive diffs, or diffs * which ignore changes in white-space. * * @param array $from_lines An array of strings. diff --git a/framework/gii/generators/model/ModelCode.php b/framework/gii/generators/model/ModelCode.php index c7734b73e1..132ceb3506 100644 --- a/framework/gii/generators/model/ModelCode.php +++ b/framework/gii/generators/model/ModelCode.php @@ -186,7 +186,7 @@ public function validateBaseClass($attribute,$params) if(!is_string($class) || !$this->classExists($class)) $this->addError('baseClass', "Class '{$this->baseClass}' does not exist or has syntax error."); elseif($class!=='CActiveRecord' && !is_subclass_of($class,'CActiveRecord')) - $this->addError('baseClass', "'{$this->model}' must extend from CActiveRecord."); + $this->addError('baseClass', "'{$this->baseClass}' must extend from CActiveRecord."); } public function getTableSchema($tableName) @@ -210,6 +210,7 @@ public function generateLabels($table) $label=substr($label,0,-3); if($label==='Id') $label='ID'; + $label=str_replace("'","\\'",$label); $labels[$column->name]=$label; } } @@ -362,7 +363,7 @@ protected function generateRelations() * Checks if the given table is a "many to many" pivot table. * Their PK has 2 fields, and both of those fields are also FK to other separate tables. * @param CDbTableSchema table to inspect - * @return boolean true if table matches description of helpter table. + * @return boolean true if table matches description of helper table. */ protected function isRelationTable($table) { diff --git a/framework/gii/generators/model/templates/default/model.php b/framework/gii/generators/model/templates/default/model.php index 7e4b14838d..4c605547c9 100644 --- a/framework/gii/generators/model/templates/default/model.php +++ b/framework/gii/generators/model/templates/default/model.php @@ -98,7 +98,7 @@ public function attributeLabels() { return array( $label): ?> - '$label',\n"; ?> + '".str_replace("'","\'",$label)."',\n"; ?> ); } diff --git a/framework/i18n/CGettextMessageSource.php b/framework/i18n/CGettextMessageSource.php index 77d7b66bbb..881d0981f4 100644 --- a/framework/i18n/CGettextMessageSource.php +++ b/framework/i18n/CGettextMessageSource.php @@ -11,7 +11,7 @@ /** * CGettextMessageSource represents a message source that is based on GNU Gettext. * - * Each CGettextMessageSource instance represents the message tranlations + * Each CGettextMessageSource instance represents the message translations * for a single domain. And each message category represents a message context * in Gettext. Translated messages are stored as either a MO or PO file, * depending on the {@link useMoFile} property value. diff --git a/framework/i18n/CLocale.php b/framework/i18n/CLocale.php index a780a14c41..767bf227b6 100644 --- a/framework/i18n/CLocale.php +++ b/framework/i18n/CLocale.php @@ -354,7 +354,7 @@ public function getScriptID($id) if(($underscorePosition=strpos($id, '_'))!==false) { $subTag = explode('_', $id); - // script sub tags can be distigused from territory sub tags by length + // script sub tags can be distinguished from territory sub tags by length if (strlen($subTag[1])===4) { $id = $subTag[1]; @@ -386,7 +386,7 @@ public function getTerritoryID($id) if (($underscorePosition=strpos($id, '_'))!== false) { $subTag = explode('_', $id); - // territory sub tags can be distigused from script sub tags by length + // territory sub tags can be distinguished from script sub tags by length if (isset($subTag[2]) && strlen($subTag[2])<4) { $id = $subTag[2]; @@ -422,13 +422,13 @@ public function getLocaleDisplayName($id=null, $category='languages') { return $this->_data[$category][$id]; } - elseif (($category == 'scripts') && ($id=$this->getScriptID($id)) && (isset($this->_data[$category][$id]))) + elseif (($category == 'scripts') && ($val=$this->getScriptID($id)) && (isset($this->_data[$category][$val]))) { - return $this->_data[$category][$id]; + return $this->_data[$category][$val]; } - elseif (($category == 'territories') && ($id=$this->getTerritoryID($id)) && (isset($this->_data[$category][$id]))) + elseif (($category == 'territories') && ($val=$this->getTerritoryID($id)) && (isset($this->_data[$category][$val]))) { - return $this->_data[$category][$id]; + return $this->_data[$category][$val]; } elseif (isset($this->_data[$category][$id])) { @@ -469,4 +469,4 @@ public function getTerritory($id) { return $this->getLocaleDisplayName($id, 'territories'); } -} \ No newline at end of file +} diff --git a/framework/logging/CEmailLogRoute.php b/framework/logging/CEmailLogRoute.php index b65a92c4f9..aa7b892f88 100644 --- a/framework/logging/CEmailLogRoute.php +++ b/framework/logging/CEmailLogRoute.php @@ -78,7 +78,7 @@ protected function sendEmail($email,$subject,$message) if($this->utf8) { $headers[]="MIME-Version: 1.0"; - $headers[]="Content-type: text/plain; charset=UTF-8"; + $headers[]="Content-Type: text/plain; charset=UTF-8"; $subject='=?UTF-8?B?'.base64_encode($subject).'?='; } if(($from=$this->getSentFrom())!==null) diff --git a/framework/messages/config.php b/framework/messages/config.php index cb5828608b..e7fa9f326f 100644 --- a/framework/messages/config.php +++ b/framework/messages/config.php @@ -6,7 +6,7 @@ return array( 'sourcePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', 'messagePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'messages', - 'languages'=>array('fi','zh_cn','zh_tw','ca','de','el','es','sv','he','nl','pt','pt_br','ru','it','fr','ja','pl','hu','ro','id','vi','bg','lv','sk','uk','ko_kr','kk','cs'), + 'languages'=>array('fi','zh_cn','zh_tw','ca','de','el','es','sv','he','nl','pt','pt_br','ru','it','fr','ja','pl','hu','ro','id','vi','bg','lv','sk','uk','ko_kr','kk','cs','da'), 'fileTypes'=>array('php'), 'overwrite'=>true, 'exclude'=>array( diff --git a/framework/messages/da/yii.php b/framework/messages/da/yii.php new file mode 100644 index 0000000000..2e014083bb --- /dev/null +++ b/framework/messages/da/yii.php @@ -0,0 +1,282 @@ + '"{path}" er ikke en gyldig mappe.', + '< Previous' => '< Forrige', + '<< First' => '<< Første', + 'A PHP extension stopped the file upload.' => 'En PHP extension stoppede uploadning af filen.', + 'Action class {class} must implement the "run" method.' => 'Action-klassen {class} skal implementere "run" funktionen.', + 'Active Record requires a "db" CDbConnection application component.' => 'Active Record kræver en "db" CDbConnection applikationskomponent.', + 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'Active Record "{class}" har en ugyldig konfiguration for relationen "{relation}". Den må specificere relationstype, den relaterede Active Record-klasse og en primary key.', + 'Active record "{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.' => 'Active record "{class}" prøver at vælge en ugyldig kolonne "{column}". Bemærk at kolonnen skal eksistere i en tabel eller være et udtryk med et alias.', + 'Adding a foreign key constraint to an existing table is not supported by SQLite.' => 'Tilføjelse af en foreign key constraint til en eksisternde tabel er ikke supporteret af SQLite.', + 'Adding a primary key after table has been created is not supported by SQLite.' => 'Tilføjelse af en primary key efter tabellen er oprettet er ikke supporteret af SQLite.', + 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'Alias "{alias}" er ugyldig. Tjek at det peger på en eksisterende PHP fil og filen er læsbar.', + 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'Alias "{alias}" er ugyldig. Tjek at den peger på en eksisterende mappe eller fil.', + 'Altering a DB column is not supported by SQLite.' => 'Ændring af en DB-kolonne er ikke supporteret af SQLite.', + 'Application Log' => 'Applikationslog', + 'Application base path "{path}" is not a valid directory.' => 'Applikationens base path "{path}" er ikke en gyldig mappe.', + 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.' => 'Applikationens runtime-mappe "{path}" er ikke gyldig. Tjek at det er en mappe som webserveren kan skrive til.', + 'Authorization item "{item}" has already been assigned to user "{user}".' => 'Brugeren "{user}" er allerede tildelt autorisationsobjektet "{item}"', + 'Base path "{path}" is not a valid directory.' => 'Base path "{path}" er ikke en gyldig mappe.', + 'CApcCache requires PHP apc extension to be loaded.' => 'CApcCache kræver at PHP extension APC er loaded.', + 'CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.' => 'CAssetManager.basePath "{path}" er ugyldig. Tjek at mappen eksisterer og at webserveren kan skrive til den.', + 'CCacheHttpSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.' => 'CCacheHttpSession.cacheID er ugyldig. Tjek at "{id}" refererer til en gyldig cache-applikationskomponent.', + 'CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.' => 'CCaptchaValidator.action "{id}" er ugyldig. Actionen blev ikke fundet i den aktuelle controller .', + 'CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbAuthManager.connectionID "{id}" er ugyldig. Tjek at den refererer til ID\'et på en CDbConnection applikationskomponent.', + 'CDbCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCache.connectionID "{id}" er ugyldig. Tjek at den refererer til ID\'et på en CDbConnection applikationskomponent.', + 'CDbCacheDependency.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCacheDependency.connectionID "{id}" er ugyldig. Tjek at det refererer til ID´et af en CDbConnection applikations-komponent.', + 'CDbCacheDependency.sql cannot be empty.' => 'CDbCacheDependency.sql kan ikke være blank.', + 'CDbCommand failed to execute the SQL statement: {error}' => 'CDbCommand kunne ikke eksekvere SQL udtrykket: {error}', + 'CDbCommand failed to prepare the SQL statement: {error}' => 'CDbCommand kunne ikke forberede SQL udtrykket: {error}', + 'CDbCommand::execute() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::execute() fejlede: {error}. Den udførte SQL-sætning var: {sql}.', + 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::{method}() fejlede: {error}. Den udførte SQL-sætning var: {sql}.', + 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection understøtter ikke at læse skema for {driver} database.', + 'CDbConnection failed to open the DB connection.' => 'CDbConnection fejlede under åbningen af DB-forbindelsen.', + 'CDbConnection is unable to find PDO class "{className}". Make sure PDO is installed correctly.' => 'CDbConnection er ikke i stand til at finde PDO-klassen "{className}". Tjek at PDO er installeret korrekt.', + 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader kan ikke spole tilbage. Den kan kun læse fremad.', + 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbHttpSession.connectionID "{id}" er ugyldig. Tjek at "{id}" refererer til en CDbConnection applikationskomponent.', + 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'CDbLogRoute.connectionID "{id}" peger ikke på en gylding CDbConnection applikationskomponent.', + 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'CDbMessageSource.connectionID er ugyldig. Tjek at "{id}" refererer til en gyldig database applikationskomponent.', + 'CDbTestFixture.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbTestFixture.connectionID "{id}" er ugyldig. Tjek at det refererer til ID´et af en CDbConnection applikations-komponent.', + 'CDbTransaction is inactive and cannot perform commit or roll back operations.' => 'CDbTransaction er inaktiv og kan ikke udføre "commit" eller "roll back" operationer.', + 'CDirectoryCacheDependency.directory cannot be empty.' => 'CDirectoryCacheDependency.directory kan ikke være blank.', + 'CEAcceleratorCache requires PHP eAccelerator extension to be loaded, enabled or compiled with the "--with-eaccelerator-shared-memory" option.' => 'CEAcceleratorCache kræver at PHP eAccelerator extension er loaded, aktiveret eller compiled med "--with-eaccelerator-shared-memory".', + 'CFileCacheDependency.fileName cannot be empty.' => 'CFileCacheDependency.fileName kan ikke være blank.', + 'CFileLogRoute.logPath "{path}" does not point to a valid directory. Make sure the directory exists and is writable by the Web server process.' => 'CFileLogRoute.logPath "{path}" peger ikke på en gyldig mappe. Tjek at mappen eksisterer og at webserveren kan skrive til den.', + 'CFilterChain can only take objects implementing the IFilter interface.' => 'CFilterChain godtager kun objekter som implementerer IFilter interface.', + 'CFlexWidget.baseUrl cannot be empty.' => 'CFlexWidget.baseUrl kan ikke være blank.', + 'CFlexWidget.name cannot be empty.' => 'CFlexWidget.name kan ikke være blank.', + 'CGlobalStateCacheDependency.stateName cannot be empty.' => 'CGlobalStateCacheDependency.stateName kan ikke være blank.', + 'CHttpCacheFilter.lastModified contained a value that could not be understood by strtotime()' => 'CHttpCacheFilter.lastModified indeholdt en værdi, som ikke kunne forstås af strtotime()', + 'CHttpCookieCollection can only hold CHttpCookie objects.' => 'CHttpCookieCollection kan kun indeholde objekter af klassen CHttpCookie.', + 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest er ikke i stand til at finde URL´en til startscriptet.', + 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest er ikke i stand til at bestemme path info for forespøgslen.', + 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest er ikke i stand til at bestemme forespørsels-URI´en.', + 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'CHttpSession.cookieMode kan kun være "none", "allow" eller "only".', + 'CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.' => 'CHttpSession.gcProbability "{value}" er ugyldig. Det skal være en float mellem 0 og 100.', + 'CHttpSession.savePath "{path}" is not a valid directory.' => 'CHttpSession.savePath "{path}" er ikke en gyldig mappe.', + 'CMemCache requires PHP {extension} extension to be loaded.' => 'CMemCache kræver at PHP {extension} extension er indlæst.', + 'CMemCache server configuration must be an array.' => 'CMemCache server konfiguration skal være et array.', + 'CMemCache server configuration must have "host" value.' => 'CMemCache server konfiguration skal have "host" værdi.', + 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute fandt en mismatchende kodeblock "{token}". Tjek at kaldene til Yii::beginProfile() og Yii::endProfile() er korrekt indlejret.', + 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".' => 'CProfileLogRoute.report "{report}" er ugyldig. Gyldige værdier inkluderer "summary" og "callstack".', + 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager kræver PHP mcrypt extension er loaded for at kunne bruge sin datakrypterings feature.', + 'CSecurityManager.encryptionKey cannot be empty.' => 'CSecurityManager.encryptionKey kan ikke være blank.', + 'CSecurityManager.validationKey cannot be empty.' => 'CSecurityManager.validationKey kan ikke være blank.', + 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() kan ikke generere en tilfældig streng i det nuværende miljø.', + 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> kan kun indeholde objekter af {type} klassen.', + 'CTypedMap<{type}> can only hold objects of {type} class.' => 'CTypedMap<{type}> kan kun indeholde objekter af klassen {type}.', + 'CUrlManager.UrlFormat must be either "path" or "get".' => 'CUrlManager.UrlFormat skal være "path" eller "get".', + 'CWinCache requires PHP wincache extension to be loaded.' => 'CWinCache kræver at PHP wincache extension er indlæst.', + 'CWinCache user cache is disabled. Please set wincache.ucenabled to On in your php.ini.' => 'CWinCache user cache er deaktiveret. Sæt venligst wincache.ucenabled til On i din php.ini.', + 'CXCache requires PHP XCache extension to be loaded.' => 'CXCache kræver at PHP extension XCache er loaded.', + 'CZendDataCache requires PHP Zend Data Cache extension to be loaded.' => 'CZendDataCache kræver at PHP extension Zend Data Cache er loaded.', + 'Cannot add "{child}" as a child of "{name}". A loop has been detected.' => 'Kan ikke tilføje "{child}" som child af "{name}". En løkke er opdaget.', + 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'Kan ikke tilføje "{child}" som child af "{parent}". En løkke er opdaget.', + 'Cannot add "{name}" as a child of itself.' => 'Kan ikke tilføje "{name}" som et child af sig selv.', + 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'Kan ikke tilføje et element af typen "{child}" til et element af typen "{parent}".', + 'Class name "{class}" does not match class file "{file}".' => 'Klassens navn "{class}" matcher ikke klassens fil "{file}".', + 'Column name must be either a string or an array.' => 'Kolonnenavn skal være enten en streng eller et array.', + 'Dropping DB column is not supported by SQLite.' => 'Sletning af en DB-kolonne er ikke supporteret af SQLite.', + 'Dropping a foreign key constraint is not supported by SQLite.' => 'Sletning af en foreign key constraint er ikke supporteret af SQLite.', + 'Either "{parent}" or "{child}" does not exist.' => 'Enten "{parent}" eller "{child}" eksisterer ikke.', + 'Error: Table "{table}" does not have a primary key.' => 'Fejl: Tabellen "{table}" har ingen primary key.', + 'Error: Table "{table}" has a composite primary key which is not supported by crud command.' => 'Fejl: Tabellen "{table}" har en sammensat primary key som ikke understøttes af CRUD-kommandoen.', + 'Event "{class}.{event}" is attached with an invalid handler "{handler}".' => 'Event "{class}.{event}" er tilknyttet en ugyldig handler "{handler}".', + 'Event "{class}.{event}" is not defined.' => 'Event "{class}.{event}" er ikke defineret.', + 'Extension path "{path}" does not exist.' => 'Extension stien "{path}" eksisterer ikke.', + 'Failed to initialize the mcrypt module.' => 'Kunne ikke initialiserer mcrypt modulet.', + 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Kunne ikke angive den usikre attribut "{attribute}" i "{class}".', + 'Failed to start session.' => 'Kunne ikke starte session.', + 'Failed to write the uploaded file "{file}" to disk.' => 'Kunne ikke gemme den uploadede filen "{file}" til disk.', + 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'Filter "{filter}" er ugyldig. Controller "{class}" har ikke filter-funktionen "filter{filter}".', + 'GD with FreeType or ImageMagick PHP extensions are required.' => 'GD med FreeType eller ImageMagick PHP extensions er påkrævet.', + 'Get a new code' => 'Hent ny kode', + 'Go to page: ' => 'Gå til side: ', + 'In order to use MIME-type validation provided by CFileValidator fileinfo PECL extension should be installed.' => 'For at bruge MIME-type validering leveret af CFileValidator, skal fileinfo PECL extension være installeret.', + 'Internal error while generating hash.' => 'Intern fejl under generering af hash.', + 'Invalid MO file revision: {revision}.' => 'Ugyldig MO-fil revision: {revision}.', + 'Invalid MO file: {file} (magic: {magic}).' => 'Ugyldig MO-fil: {file} (magic: {magic}).', + 'Invalid enumerable value "{value}". Please make sure it is among ({enum}).' => 'Ugyldig enumererbar værdi "{value}". Tjek at den er blandt ({enum}).', + 'Invalid expression for CHttpCacheFilter.lastModifiedExpression: The evaluation result "{value}" could not be understood by strtotime()' => 'Ugyldigt udtryk for CHttpCacheFilter.lastModifiedExpression: Det evaluerede resultat "{value}" kunne ikke forstås af strtotime()', + 'Invalid operator "{operator}".' => 'Ugyldig operator "{operator}".', + 'Last >>' => 'Sidste >>', + 'List data must be an array or an object implementing Traversable.' => 'Listedata skal være et array eller et objekt som implementerer Traversable.', + 'List index "{index}" is out of bound.' => 'Listeindeks "{index}" er udenfor område.', + 'Login Required' => 'Pålogging påkrævet', + 'Map data must be an array or an object implementing Traversable.' => 'Mapdata må være et array eller et objekt som implementerer Traversable.', + 'Missing the temporary folder to store the uploaded file "{file}".' => 'Mangler midlertidig mappe til at gemme den uploadede fil "{file}".', + 'Next >' => 'Næste >', + 'No' => 'Nej', + 'No columns are being updated for table "{table}".' => 'Ingen kolonner bliver opdateret for tabellen "{table}".', + 'No counter columns are being updated for table "{table}".' => 'Ingen tæller-kolonmner bliver opdateret for tabellen "{table}".', + 'Object configuration must be an array containing a "class" element.' => 'Konfiguration for objektet skal være et array indeholdende et "class" element.', + 'Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.' => 'SHA1 og MD5 er de eneste hashing algoritmer, som er supporteret i PHP 5.1.1 og under.', + 'Please fix the following input errors:' => 'Ret venligst følgende indtastnings-fejl:', + 'Powered by {yii}.' => 'Drevet af {yii}.', + 'Property "{class}.{property}" is not defined.' => 'Egenskaben "{class}.{property}" er ikke defineret.', + 'Property "{class}.{property}" is read only.' => 'Egenskaben "{class}.{property}" er skrivebeskyttet.', + 'Property CMaskedTextField.mask cannot be empty.' => 'Property CMaskedTextField.mask kan ikke være blank.', + 'Queue data must be an array or an object implementing Traversable.' => 'Queue-data skal være et array eller et objekt som implementerer Traversable.', + 'Relation "{name}" is not defined in active record class "{class}".' => 'Relation "{name}" er ikke defineret i "active record" klassen "{class}.', + 'Removing a primary key after table has been created is not supported by SQLite.' => 'Fjernelsen af en primary key efter tabellen er oprettet er ikke supporteret af SQLite.', + 'Renaming a DB column is not supported by SQLite.' => 'Omdøbning af en DB-kolonne er ikke supporteret af SQLite.', + 'Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".' => 'Script HTML options er ikke tilladt for "CClientScript::POS_LOAD" og "CClientScript::POS_READY".', + 'Stack data must be an array or an object implementing Traversable.' => 'Stack-data skal være et array eller et objekt som implementerer Traversable.', + 'Table "{table}" does not exist.' => 'Tabellen "{table}" eksisterer ikke.', + 'Table "{table}" does not have a column named "{column}".' => 'Tabellen "{table}" har ikke en kolonne som hedder "{column}"', + 'The "db" application component must be configured to be a CDbConnection object.' => '"db" applikationskomponenten skal være konfigureret til at været et CDbConnection objekt.', + 'The "filter" property must be specified with a valid callback.' => '"filter" egenskaben skal være specificeret med et gyldigt "callback".', + 'The "forceCopy" and "linkAssets" cannot be both true.' => '"forceCopy" og "linkAssets" kan ikke begge være true.', + 'The "pattern" property must be specified with a valid regular expression.' => '"pattern" egenskaben skal være specificeret med en gyldig regular expression', + 'The "range" property must be specified with a list of values.' => '"range" skal være specificeret med en liste af værdier.', + 'The $converter argument must be a valid callback or null.' => '$converter argumentet skal være et gyldigt callback eller null.', + 'The CSRF token could not be verified.' => 'CSRF tokenen kunne ikke verifiseres.', + 'The STAT relation "{name}" cannot have child relations.' => 'Relationsnavn for STAT "{name}" kan ikke have child-relationer.', + 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'URL-mønster "{pattern}" for routen "{route}" er ikke en gyldig regular expression.', + 'The active record cannot be deleted because it is new.' => 'Active recorden kan ikke slettes fordi den er ny.', + 'The active record cannot be inserted to database because it is not new.' => 'Active recorden kan ikke indsættes fordi den ikke er ny.', + 'The active record cannot be updated because it is new.' => 'Active record kan ikke opdateres fordi den er ny.', + 'The asset "{asset}" to be published does not exist.' => 'Assetten "{asset}" som skulle publiseres eksisterer ikke.', + 'The command path "{path}" is not a valid directory.' => 'Command-mappen "{path}" er ikke en gyldig mappe.', + 'The controller path "{path}" is not a valid directory.' => 'Controller-mappen "{path}" er ikke en gyldig mappe.', + 'The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.' => 'Filen "{file}" kunne ikke uploades. Kun filer af disse MIME-typer er tilladte: {mimeTypes}.', + 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.' => 'Filen "{file}" kan ikke uploades. Kun filer af følgende typer er tilladte: {extensions}.', + 'The file "{file}" is too large. Its size cannot exceed {limit} bytes.' => 'Filen "{file}" er for stor. Størrelsen må ikke overskride {limit} bytes.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.' => 'Filen "{file}" er for lille. Størrelsen må ikke være mindre end {limit} bytes.', + 'The file "{file}" was only partially uploaded.' => 'Filen "{file}" blev kun delvist uploadet.', + 'The first element in a filter configuration must be the filter class.' => 'Det første elementet i en filter-konfiguration skal være filter-klassen.', + 'The format of {attribute} is invalid.' => 'Formatet af {attribute} er ugyldigt.', + 'The item "{name}" does not exist.' => 'Elementet "{name}" eksisterer ikke.', + 'The item "{parent}" already has a child "{child}".' => 'Elementet "{parent}" har allerede et child "{child}".', + 'The layout path "{path}" is not a valid directory.' => 'Layout-stien "{path}" er ikke en gyldig mappe.', + 'The list is read only.' => 'Listen er skrivebeskyttet.', + 'The map is read only.' => 'Map´et er skrivebeskyttet.', + 'The module path "{path}" is not a valid directory.' => 'Modul-stien "{path}" er ikke en gyldig mappe.', + 'The pattern for 12 hour format must be "h" or "hh".' => 'Syntaksen for 12 timers format skal være "h" eller "hh".', + 'The pattern for 24 hour format must be "H" or "HH".' => 'Syntaksen for 24 timers format skal være "H" eller "HH".', + 'The pattern for AM/PM marker must be "a".' => 'Syntaksen for "AM/PM" skal være "a".', + 'The pattern for day in month must be "F".' => 'Syntaksen for "day in month" skal være "F".', + 'The pattern for day in year must be "D", "DD" or "DDD".' => 'Syntaksen for "day in year" skal være "D", "DD" eller "DDD".', + 'The pattern for day of the month must be "d" or "dd".' => 'Syntaksen for "day of the month" skal være "d" eller "dd".', + 'The pattern for day of the week must be "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" or "ccccc".' => 'Syntaksen for "day of the week" skal være "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" eller "ccccc".', + 'The pattern for era must be "G", "GG", "GGG", "GGGG" or "GGGGG".' => 'Syntaksen for "era" skal være "G", "GG", "GGG", "GGGG" eller "GGGGG".', + 'The pattern for hour in AM/PM must be "K" or "KK".' => 'Syntaksen for "hour in AM/PM" skal være "K" eller "KK".', + 'The pattern for hour in day must be "k" or "kk".' => 'Syntaksen for "hour in day" skal være "k" eller "kk".', + 'The pattern for minutes must be "m" or "mm".' => 'Syntaksen for "minutes" skal være "m" eller "mm".', + 'The pattern for month must be "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" or "LLLL".' => 'Syntaksen for "month" skal være "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" eller "LLLL".', + 'The pattern for seconds must be "s" or "ss".' => 'Syntaksen for "seconds" skal være "s" eller "ss".', + 'The pattern for time zone must be "z" or "v".' => 'Syntaksen for "time zone" skal være "z" eller "v".', + 'The pattern for week in month must be "W".' => 'Syntaksen for "week in month" skal være "W".', + 'The pattern for week in year must be "w".' => 'Syntaksen for "week in year" skal være "w".', + 'The queue is empty.' => 'Køen er tom.', + 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Relationen "{relation}" i Active Record klassen "{class}" er ikke specificeret korrekt: Join tabellen "{joinTable}" givet i primary key findes ikke i databasen.', + 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'Relationen "{relation}" i Active Record klassen "{class}" er specificeret med en primary key som ikke peger på parent tabellen "{table}".', + 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'Relationen "{relation}" i Active Record klassen "{class}" er specificeret med en ufuldstændig primary key. Primary key skal bestå af kolonner som refererer til begge tabeller som bliver joinet.', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".' => 'Relationen "{relation}" i Active Record klassen "{class}" er specificeret med en ugyldig primary key "{key}". Det er ingen sådan kolonne i tabellen "{table}".', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "{table}".' => 'Relationen "{relation}" i Active Record klassen "{class}" er specificeret med en ugyldig primary key. Kolonnerne i nøglen skal matche primary keys i tabellen "{table}".', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".' => 'Relationen "{relation}" i Active Record klassen "{class}" er specificeret med en ugyldig primary key. Formatet for primary key skal være "joinTable(fk1,fk2,...)".', + 'The requested view "{name}" was not found.' => 'Den ønskede visning "{name}" blev ikke fundet.', + 'The stack is empty.' => 'Stakken er tom.', + 'The system is unable to find the requested action "{action}".' => 'Systemet kunne ikke finde den ønskede handling "{action}".', + 'The system view path "{path}" is not a valid directory.' => 'Systemts view path "{path}" er ikke en gyldig mappe.', + 'The table "{table}" for active record class "{class}" cannot be found in the database.' => 'Tabellen "{table}" for active record klassen "{class}" blev ikke fundet i databasen.', + 'The value for the column "{column}" is not supplied when querying the table "{table}".' => 'Værdien for kolonnen "{column}" er ikke angivet ved opslag i tabellen "{table}".', + 'The verification code is incorrect.' => 'Verifikationskoden er ikke korrekt.', + 'The view path "{path}" is not a valid directory.' => 'View path "{path}" er ikke en gylding mappe.', + 'Theme directory "{directory}" does not exist.' => 'Theme-mappe "{directory}" eksisterer ikke.', + 'This content requires the Adobe Flash Player.' => 'Dette indhold kræver Adobe Flash Player.', + 'Unable to add an item whose name is the same as an existing item.' => 'Kan ikke tilføje et element med samme navn som et element som allerede eksisterer.', + 'Unable to change the item name. The name "{name}" is already used by another item.' => 'Kan ikke ændret navn på elementet. Navnet "{name}" er allerede i brug af et andet element.', + 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'Kunne ikke oprette applikation state fil "{file}". Tjek at den eksisterer og at webserveren kan skrive til den.', + 'Unable to find "{column}" in table "{table}".' => '"{column}" blev ikke fundet i tabellen "{table}".', + 'Unable to generate random string.' => 'Kunne ikke generere en tilfældig streng.', + 'Unable to lock file "{file}" for reading.' => 'Kan ikke låse filen "{file}" for læsing.', + 'Unable to lock file "{file}" for writing.' => 'Kan ikke låse filen "{file}" for skriving.', + 'Unable to read file "{file}".' => 'Kan ikke læse filen "{file}".', + 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'Kan ikke gentage handlingen "{object}.{method}". Funktionen eksisterer ikke.', + 'Unable to resolve the request "{route}".' => 'Kan ikke rute denne forespøgsel "{route}".', + 'Unable to upload the file "{file}" because of an unrecognized error.' => 'Kunne ikke uploade filen "{file}" på grund af en ukendt fejl.', + 'Unable to write file "{file}".' => 'Kunne ikke skrive til filen "{file}".', + 'Unknown authorization item "{name}".' => 'Ukendt autoriseringselement "{name}".', + 'Unknown operator "{operator}".' => 'Ukendt operator "{operator}".', + 'Unknown type "{type}".' => 'Ukendt type "{type}".', + 'Unrecognized locale "{locale}".' => 'Ukendt locale "{locale}".', + 'View file "{file}" does not exist.' => 'View-filen "{file}" eksisterer ikke.', + 'Yes' => 'Ja', + 'Yii application can only be created once.' => 'Yii-applikationen kan kun oprettes en gang.', + 'You are not authorized to perform this action.' => 'Du er ikke autoriseret til at udføre denne handling.', + 'Your request is invalid.' => 'Din forespøgsel er ugyldig.', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" er allerede taget.', + '{attribute} "{value}" is invalid.' => '{attribute} "{value}" er ugyldig.', + '{attribute} cannot accept more than {limit} files.' => '{attribute} kan ikke accepterer mere end {limit} filer.', + '{attribute} cannot be blank.' => '{attribute} kan ikke være blank.', + '{attribute} is in the list.' => '{attribute} er i listen.', + '{attribute} is invalid.' => '{attribute} er ugyldig.', + '{attribute} is not a valid URL.' => '{attribute} er ikke en gyldig URL.', + '{attribute} is not a valid email address.' => '{attribute} er ikke en gyldig emailadresse.', + '{attribute} is not in the list.' => '{attribute} er ikke i listen.', + '{attribute} is of the wrong length (should be {length} characters).' => '{attribute} har forkert længde (bør være {length} tegn).', + '{attribute} is too big (maximum is {max}).' => '{attribute} er for stor (maksimalt er {max}).', + '{attribute} is too long (maximum is {max} characters).' => '{attribute} er for lang (maksimalt er {max} tegn).', + '{attribute} is too short (minimum is {min} characters).' => '{attribute} er for kort (minimum er {min} tegn)', + '{attribute} is too small (minimum is {min}).' => '{attribute} er for lille (minimum er {min}', + '{attribute} must be a number.' => '{attribute} skal være et tal.', + '{attribute} must be an integer.' => '{attribute} skal være et heltal.', + '{attribute} must be either {true} or {false}.' => '{attribute} skal være {true} eller {false}.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} skal være større end "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} skal være større end eller lig med "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} skal være mindre end "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} skal være mindre end eller lig med "{compareValue}".', + '{attribute} must be repeated exactly.' => '{attribute} skal gentages nøjagtigt.', + '{attribute} must be {type}.' => '{attribute} skal være {type}.', + '{attribute} must be {value}.' => '{attribute} skal være {value}.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} må ikke være lig med "{compareValue}".', + '{className} does not support add() functionality.' => '{className} understøtter ikke add()-funktionalitet.', + '{className} does not support delete() functionality.' => '{className} understøtter ikke delete()-funktionalitet.', + '{className} does not support flushValues() functionality.' => '{className} supporterer ikke flushValues() funktionalitet.', + '{className} does not support get() functionality.' => '{className} understøtter ikke get()-funktionalitet.', + '{className} does not support set() functionality.' => '{className} understøtter ikke set()-funktionalitet.', + '{class} and its behaviors do not have a method or closure named "{name}".' => '{class} og dens "behaviors" har ikke en funktion eller closure kaldet "{name}".', + '{class} does not have relation "{name}".' => '{class} har ingen relation "{name}".', + '{class} does not support fetching all table names.' => '{class} understøtter ikke henting af alle tabelnavne.', + '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} har en ugyldig valideringsregel. Reglen skal specificere attributer som skal valideres samt navnet på validatoren.', + '{class} must specify "model" and "attribute" or "name" property values.' => '{class} skal specificere egenskaberne "model" og "attribute" eller "name".', + '{class} requires the Blowfish option of the PHP crypt() function. This system does not have it.' => '{class} kræver Blowfish option af PHP crypt() funktionen. Dette system har den ikke.', + '{class} requires the PHP crypt() function. This system does not have it.' => '{class} kræver PHP crypt() funktionen. Dette system har den ikke.', + '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin skal sættes til "true" for at bruge cookie-baseret autentificering.', + '{class}::$cost must be a number.' => '{class}::$cost skal være et nummer.', + '{class}::$cost must be between 4 and 31.' => '{class}::$cost skal være mellem 4 og 31.', + '{class}::authenticate() must be implemented.' => '{class}::authenticate() skal implementeres.', + '{controller} cannot find the requested view "{view}".' => '{controller} kan ikke finde det ønskede view "{view}".', + '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} indeholder forkert indlejrede widget tags i sit view "{view}". En {widget} widget har ikke et endWidget()-kall.', + '{controller} has an extra endWidget({id}) call in its view.' => '{controller} har et ekstra endWidget({id})-kald i sit view.', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} TB' => '{n} TB', + '{n} byte|{n} bytes' => '{n} byte|{n} bytes', + '{n} gigabyte|{n} gigabytes' => '{n} gigabyte|{n} gigabytes', + '{n} kilobyte|{n} kilobytes' => '{n} kilobyte|{n} kilobytes', + '{n} megabyte|{n} megabytes' => '{n} megabyte|{n} megabytes', + '{n} terabyte|{n} terabytes' => '{n} terabyte|{n} terabytes', + '{widget} cannot find the view "{view}".' => '{widget} kan ikke finde view´et "{view}".', +); diff --git a/framework/messages/da/zii.php b/framework/messages/da/zii.php new file mode 100644 index 0000000000..a71fcacd19 --- /dev/null +++ b/framework/messages/da/zii.php @@ -0,0 +1,41 @@ + 'Er du sikker på at du vil slette dette element?', + 'Delete' => 'Slet', + 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Viser {start}-{end} af 1 resultat.|Viser {start}-{end} af {count} resultater.', + 'Either "name" or "value" must be specified for CDataColumn.' => 'Enten "name" eller "value" skal være defineret for CDataColumn.', + 'Home' => 'Start', + 'No results found.' => 'Ingen resultater fundet.', + 'Not set' => 'Ikke angivet', + 'Please specify the "attributes" property.' => 'Angiv venligst egenskaben "attributes".', + 'Please specify the "data" property.' => 'Angiv venligst egenskaben "data".', + 'Sort by: ' => 'Sorter efter: ', + 'The "dataProvider" property cannot be empty.' => 'Egenskaben "dataProvider" kan ikke være tom.', + 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Attributten skal være specificeret i formatet "Name:Type:Label", hvor "Type" og "Label" er valgfrie.', + 'The button type "{type}" is not supported.' => 'Knaptypen "{type}" er ikke supporteret.', + 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Kolonnen skal være specificeret i formatet "Name:Type:Label", hvor "Type" og "Label" er valgfrie.', + 'The property "itemView" cannot be empty.' => 'Egenskaben "itemView" kan ikke være tom.', + 'The property filterSelector should be defined.' => 'Egenskaben filterSelector burde være defineret.', + 'The property updateSelector should be defined.' => 'Egenskaben updateSelector burde være defineret.', + 'Total 1 result.|Total {count} results.' => 'Total 1 resultat.|Total {count} resultater.', + 'Update' => 'Opdater', + 'View' => 'Vis', + '{class} must specify "model" and "{attribute}" or "{name}" property values.' => '{class} skal specificere værdier for egenskaberne "model" og "{attribute}" eller "{name}".' , +); diff --git a/framework/messages/de/yii.php b/framework/messages/de/yii.php index 9b7436cd29..cb680ab66f 100644 --- a/framework/messages/de/yii.php +++ b/framework/messages/de/yii.php @@ -17,24 +17,12 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'Adding a primary key after table has been created is not supported by SQLite.' => 'Das Hinzufügen eines Primärschlüssels nach dem Erstellen einer Tabelle wird von SQLite nicht unterstützt.', - 'CDbConnection is unable to find PDO class "{className}". Make sure PDO is installed correctly.' => 'CDbConnection konnte die PDO Klasse "{className}" nicht finden. Stellen Sie sicher, dass PDO korrekt installiert ist.', - 'CHttpCacheFilter.lastModified contained a value that could not be understood by strtotime()' => 'CHttpCacheFilter.lastModified enthält einen Wert, der nicht von strtotime() gelesen werden kann.', - 'CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.' => 'CHttpSession.gcProbability "{value}" ist ungültig. Der Wert muss eine Fließkommazahl zwischen 0 und 100 sein.', - 'CMemCache requires PHP {extension} extension to be loaded.' => 'CMemCache benötigt die PHP Erweiterung "{extension}".', - 'Cannot hash a password that is empty or not a string.' => 'Es kann kein Hash für ein Password gebildet werden, das leer oder kein String ist.', - 'Class name "{class}" does not match class file "{file}".' => 'Der Klassenname "{class}" passt nicht zum Dateinamen "{file}".', - 'GD with FreeType or ImageMagick PHP extensions are required.' => 'Es werden entweder die PHP Erweiterung GD und FreeType oder die PHP Erweiterung ImageMagick benötigt.', - 'In order to use MIME-type validation provided by CFileValidator fileinfo PECL extension should be installed.' => 'Um die Validierung von MIME-Typen des CFileValidator zu nutzen, muss die PECL Erweiterung "fileinfo" installiert sein.', - 'Internal error while generating hash.' => 'Beim Erstellen des Hash-Wertes ist ein interner Fehler aufgetreten.', - 'Invalid expression for CHttpCacheFilter.lastModifiedExpression: The evaluation result "{value}" could not be understood by strtotime()' => 'Ungültiger Ausdruck für CHttpCacheFilter.lastModifiedExpression: Die Auswertung des Ausdrucks "{value}" kann nicht von strtotime() gelesen werden.', - 'Removing a primary key after table has been created is not supported by SQLite.' => 'Das Entfernen eines Primärschlüssels nach dem Erstellen einer Tabelle wird von SQLite nicht unterstützt.', - 'The "forceCopy" and "linkAssets" cannot be both true.' => 'Die Werte "forceCopy" und "linkAssets" können nicht gleichzeitig "true" sein.', - 'The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.' => 'Die Datei "{file}" konnte nicht hochgeladen werden. Es sind nur Dateien mit den folgenden MIME-Typen erlaubt: {mimeTypes}.', - '{class} requires the Blowfish option of the PHP crypt() function. This system does not have it.' => '{class} benötigt die Blowfish-Option der PHP-crypt()-Funktion, welche auf diesem System nicht zur verfügung steht.', - '{class} requires the PHP crypt() function. This system does not have it.' => '{class} benötigt die PHP-crypt()-Funktion, welche auf diesem System nicht zur verfügung steht.', - '{class}::$cost must be a number.' => '{class}::$cost muss eine Zahl sein.', - '{class}::$cost must be between 4 and 31.' => '{class}::$cost muss eine Zahl zwischen 4 und 31 sein.', + 'A PHP extension stopped the file upload.' => 'Der Datei-Upload wurde von einer PHP-Erweiterung angehalten.', + 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() ist auf diesem System nicht in der Lage eine zufällige Zeichenkette zu erzeugen.', + 'Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.' => 'PHP 5.1.1 oder kleiner unterstützt nur die Hashing-Algorithmen SHA1 und MD5.', + 'Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".' => 'Für "CClientScript::POS_LOAD" and "CClientScript::POS_READY" ist die Angabe von HTML-Attributen nicht erlaubt.', + 'The $converter argument must be a valid callback or null.' => 'Das $converter Argument muss ein gültiges PHP-Callback oder null sein.', + 'Unable to generate random string.' => 'Es war nicht möglich eine zufällige Zeichenkette zu erzeugen.', '"{path}" is not a valid directory.' => '"{path}" ist kein gültiges Verzeichnis.', '< Previous' => '< Vorherige', '<< First' => '<< Erste', @@ -43,9 +31,9 @@ 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'ActiveRecord-Klasse "{class}" hat eine ungültige Konfiguration für die Relation "{relation}". Relations-Typ, verknüpftes ActiveRecord und Fremdschlüssel müssen angegeben werden.', 'Active record "{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.' => 'ActiveRecord "{class}" benutzt das ungültige Feld "{column}" in SELECT. Beachten Sie, dass dieses Feld in der Tabelle existieren oder ein Alias-Ausdruck sein muss.', 'Adding a foreign key constraint to an existing table is not supported by SQLite.' => 'SQLite unterstützt das Hinzufügen eines Fremdschüssel zu einer bestehenden Tabelle nicht.', + 'Adding a primary key after table has been created is not supported by SQLite.' => 'Das Hinzufügen eines Primärschlüssels nach dem Erstellen einer Tabelle wird von SQLite nicht unterstützt.', 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'Alias "{alias}" ist ungültig. Stellen Sie sicher, dass er auf eine existierende PHP-Datei verweist und die Datei lesbar ist.', 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'Der Alias "{alias}" ist ungültig. Stellen Sie sicher, dass er auf ein existierendes Verzeichnis oder eine existierende Datei verweist.', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => 'Alias "{alias}" ist ungültig. Stellen Sie sicher, dass er auf ein existierendes Verzeichnis verweist.', 'Altering a DB column is not supported by SQLite.' => 'SQLite unterstützt das Verändern einer DB-Spalte nicht.', 'Application Log' => 'Anwendungsprotokoll', 'Application base path "{path}" is not a valid directory.' => 'Der Basispfad "{path}" der Applikation ist kein gültiges Verzeichnis.', @@ -66,6 +54,7 @@ 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::{method} fehlgeschlagen: {error}. Der SQL-Ausdruck war: {sql}.', 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection unterstützt das Lesen von Schemas für {driver}-Datenbanken nicht.', 'CDbConnection failed to open the DB connection.' => 'CDbConnection konnte die DB-Verbindung nicht aufbauen.', + 'CDbConnection is unable to find PDO class "{className}". Make sure PDO is installed correctly.' => 'CDbConnection konnte die PDO Klasse "{className}" nicht finden. Stellen Sie sicher, dass PDO korrekt installiert ist.', 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader ist nicht rewind-fähig. Es ist ein forward-only (nur-vorwärts) Leser.', 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbHttpSession.connectionID "{id}" ist ungültig. Bitte stellen Sie sicher, dass sie sich auf die ID einer Applikations-Komponente vom Typ CDbConnection bezieht.', 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'CDbLogRoute.connectionID "{id}" zeigt nicht auf eine gültige Applikations-Komponente vom Typ CDbConnection.', @@ -80,12 +69,15 @@ 'CFlexWidget.baseUrl cannot be empty.' => 'CFlexWidget.baseUrl darf nicht leer sein.', 'CFlexWidget.name cannot be empty.' => 'CFlexWidget.name darf nicht leer sein.', 'CGlobalStateCacheDependency.stateName cannot be empty.' => 'CGlobalStateCacheDependency.stateName darf nicht leer sein.', + 'CHttpCacheFilter.lastModified contained a value that could not be understood by strtotime()' => 'CHttpCacheFilter.lastModified enthält einen Wert, der nicht von strtotime() gelesen werden kann.', 'CHttpCookieCollection can only hold CHttpCookie objects.' => 'CHttpCookieCollection kann nur CHttpCookie-Objekte enthalten.', 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest kann die URL des Eingangs-Scripts nicht bestimmen.', 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest kann die Pfadinformation der Anfrage nicht ermitteln.', 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest kann die angeforderte URI nicht ermitteln.', 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'CHttpSession.cookieMode kann nur "none", "allow" oder "only" sein.', + 'CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.' => 'CHttpSession.gcProbability "{value}" ist ungültig. Der Wert muss eine Fließkommazahl zwischen 0 und 100 sein.', 'CHttpSession.savePath "{path}" is not a valid directory.' => 'CHttpSession.savePath "{path}" ist kein gültiges Verzeichnis.', + 'CMemCache requires PHP {extension} extension to be loaded.' => 'CMemCache benötigt die PHP Erweiterung "{extension}".', 'CMemCache server configuration must be an array.' => 'CMemCache Serverkonfiguration muss ein Array sein.', 'CMemCache server configuration must have "host" value.' => 'CMemCache Serverkonfiguration erfordert einen Wert für "host".', 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute fand einen unzugehörigen Code-Block "{token}". Stellen Sie sicher dass Aufrufe von Yii::beginProfile() und Yii::endProfile() richtig verschachtelt sind.', @@ -104,6 +96,7 @@ 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'Kann "{child}" nicht als Kind von "{parent}" hinzufügen. Es wurde eine Schleife entdeckt.', 'Cannot add "{name}" as a child of itself.' => 'Kann "{name}" nicht als Kind von sich selbst hinzufügen.', 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'Kann ein Element vom Typ "{child}" nicht als Kind zu einem Element vom Typ "{parent}" hinzufügen.', + 'Class name "{class}" does not match class file "{file}".' => 'Der Klassenname "{class}" passt nicht zum Dateinamen "{file}".', 'Column name must be either a string or an array.' => 'Spaltenname muss entweder ein String oder ein Array sein.', 'Dropping DB column is not supported by SQLite.' => 'SQLite untestützt das Entfernen einer DB-Spalte nicht.', 'Dropping a foreign key constraint is not supported by SQLite.' => 'SQLite unterstützt das Entfernen eines Fremdschlüssels nicht.', @@ -117,13 +110,16 @@ 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Ungesichertes Attribut "{attribute}" konnte nicht gesetzt werden.', 'Failed to start session.' => 'Session konnte nicht gestartet werden.', 'Failed to write the uploaded file "{file}" to disk.' => 'Hochgeladene Datei "{file}" konnte nicht auf die Festplatte gespeichert werden.', - 'File upload was stopped by extension.' => 'Datei-Upload wurde von einer Erweiterung angehalten.', 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'Filter "{filter}" ist ungültig. Controller "{class}" hat keine Filter-Methode "filter{filter}".', + 'GD with FreeType or ImageMagick PHP extensions are required.' => 'Es werden entweder die PHP Erweiterung GD und FreeType oder die PHP Erweiterung ImageMagick benötigt.', 'Get a new code' => 'Neuen Code erzeugen', 'Go to page: ' => 'Zu Seite: ', + 'In order to use MIME-type validation provided by CFileValidator fileinfo PECL extension should be installed.' => 'Um die Validierung von MIME-Typen des CFileValidator zu nutzen, muss die PECL Erweiterung "fileinfo" installiert sein.', + 'Internal error while generating hash.' => 'Beim Erstellen des Hash-Wertes ist ein interner Fehler aufgetreten.', 'Invalid MO file revision: {revision}.' => 'Ungültige MO-Datei-Revision: {revision}', 'Invalid MO file: {file} (magic: {magic}).' => 'Ungültige MO-Datei: {file} (magic: {magic}).', 'Invalid enumerable value "{value}". Please make sure it is among ({enum}).' => 'Ungültiger Enumerable-Wert "{value}". Bitte stellen Sie sicher er ist in ({enum}) enthalten.', + 'Invalid expression for CHttpCacheFilter.lastModifiedExpression: The evaluation result "{value}" could not be understood by strtotime()' => 'Ungültiger Ausdruck für CHttpCacheFilter.lastModifiedExpression: Die Auswertung des Ausdrucks "{value}" kann nicht von strtotime() gelesen werden.', 'Invalid operator "{operator}".' => 'Ungültiger Operator "{operator}".', 'Last >>' => 'Letzte >>', 'List data must be an array or an object implementing Traversable.' => 'List-Daten müssen ein Array sein oder ein Objekt, das das Interface Traversable implementiert.', @@ -143,12 +139,14 @@ 'Property CMaskedTextField.mask cannot be empty.' => 'Eigenschaft CMaskedTextField.mask darf nicht leer sein.', 'Queue data must be an array or an object implementing Traversable.' => 'Queue-Daten müssen ein Array sein oder ein Objekt, das das Interface Traversable implementiert.', 'Relation "{name}" is not defined in active record class "{class}".' => 'Relation "{name}" ist in der ActiveRecord-Klasse "{class}" nicht definiert.', + 'Removing a primary key after table has been created is not supported by SQLite.' => 'Das Entfernen eines Primärschlüssels nach dem Erstellen einer Tabelle wird von SQLite nicht unterstützt.', 'Renaming a DB column is not supported by SQLite.' => 'SQLite unterstützt das Umbenennen einer DB-Spalte nicht.', 'Stack data must be an array or an object implementing Traversable.' => 'Stack-Daten müssen ein Array sein oder ein Objekt, das das Interface Traversable implementiert.', 'Table "{table}" does not exist.' => 'Tabelle "{table}" existiert nicht.', 'Table "{table}" does not have a column named "{column}".' => 'Tabelle "{table}" hat kein Feld namens "{column}".', 'The "db" application component must be configured to be a CDbConnection object.' => 'Die "db"-Komponente der Anwendung muss als CDBConnection-Objekt konfigureirt sein.', 'The "filter" property must be specified with a valid callback.' => 'Für "filter" muss ein gültiger Callback angegeben werden.', + 'The "forceCopy" and "linkAssets" cannot be both true.' => 'Die Werte "forceCopy" und "linkAssets" können nicht gleichzeitig "true" sein.', 'The "pattern" property must be specified with a valid regular expression.' => 'Für "pattern" muss ein gültiger regulärer Ausdruck angegeben werden.', 'The "range" property must be specified with a list of values.' => 'Die "range"-Eigenschaft muss eine Liste von Werten enthalten.', 'The CSRF token could not be verified.' => 'Der CSRF-Token konnte nicht verifiziert werden.', @@ -161,6 +159,7 @@ 'The asset "{asset}" to be published does not exist.' => 'Das zu veröffentlichende Asset "{asset}" existiert nicht.', 'The command path "{path}" is not a valid directory.' => 'Der Kommando-Pfad "{path}" ist kein gültiges Verzeichnis.', 'The controller path "{path}" is not a valid directory.' => 'Der Controller-Pfad "{path}" ist kein gültiges Verzeichnis.', + 'The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.' => 'Die Datei "{file}" konnte nicht hochgeladen werden. Es sind nur Dateien mit den folgenden MIME-Typen erlaubt: {mimeTypes}.', 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.' => 'Die Datei "{file}" kann nicht hochgeladen werden. Nur Dateien mit diesen Endungen sind erlaubt: {extensions}.', 'The file "{file}" is too large. Its size cannot exceed {limit} bytes.' => 'Die Datei "{file}" ist zu groß. Die Größe kann {limit} Bytes nicht überschreiten.', 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.' => 'Die Datei "{file}" ist zu klein. Die Größe kann {limit} Bytes nicht unterschreiten.', @@ -190,7 +189,6 @@ 'The pattern for week in month must be "W".' => 'Das Schema für die Woche im Monat muss "W" lauten.', 'The pattern for week in year must be "w".' => 'Das Schema für Kalenderwochen muss "w" lauten.', 'The queue is empty.' => 'Die Queue ist leer.', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Die Relation "{relation}" in der ActiveRecord-Klasse "{class}" ist nicht korrekt definiert: Die im Fremdschlüssel angegebene Join-Tabelle "{joinTable}" wurde in der Datenbank nicht gefunden', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Die Relation "{relation}" in der ActiveRecord-Klasse "{class}" ist nicht korrekt definiert: Die im Fremdschlüssel verwendete Join-Tabelle "{joinTable}" wurde in der Datenbank nicht gefunden.', 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'Die Relation "{relation}" in der ActiveRecord-Klasse "{class}" wurde mit dem Fremschlüssel "{key}" angegeben, der nicht auf die Elterntabelle "{table}" verweist.', 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'Zur Relation "{relation} in der ActiveRecord-Klasse "{class}" wurde ein unvollständiger Fremdschlüssel angegeben. Der Fremdschlüssel muss aus Feldern bestehen, die sich auf beide zu joinende Tabellen beziehen.', @@ -261,7 +259,11 @@ '{class} does not support fetching all table names.' => '{class} unterstützt das Beziehen aller Tabellennamen nicht.', '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} hat eine ungültige Validierungs-Regel. Die Regel muss die zu validierenden Attribute und den Validatornamen enthalten.', '{class} must specify "model" and "attribute" or "name" property values.' => '{class} muss "model" und "attribute" oder "name" Eigenschaften festlegen.', + '{class} requires the Blowfish option of the PHP crypt() function. This system does not have it.' => '{class} benötigt die Blowfish-Option der PHP-crypt()-Funktion, welche auf diesem System nicht zur verfügung steht.', + '{class} requires the PHP crypt() function. This system does not have it.' => '{class} benötigt die PHP-crypt()-Funktion, welche auf diesem System nicht zur verfügung steht.', '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin muss auf true gesetzt werden, um cookie-basierte Authentifizierung zu verwenden.', + '{class}::$cost must be a number.' => '{class}::$cost muss eine Zahl sein.', + '{class}::$cost must be between 4 and 31.' => '{class}::$cost muss eine Zahl zwischen 4 und 31 sein.', '{class}::authenticate() must be implemented.' => '{class}::authenticate() muss implementiert werden.', '{controller} cannot find the requested view "{view}".' => '{controller} kann den angeforderten View "{view}" nicht finden.', '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} enthält falsch verschachtelte Widget-Tags im View "{view}". Ein {widget}-Widget hat keinen endwidget()-Aufruf.', diff --git a/framework/messages/de/zii.php b/framework/messages/de/zii.php index 9533dffac0..7c90119dbe 100644 --- a/framework/messages/de/zii.php +++ b/framework/messages/de/zii.php @@ -17,8 +17,7 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'The property filterSelector should be defined.' => 'Die Eigenschaft "filterSelector" muss definiert werden.', - 'The property updateSelector should be defined.' => 'Die Eigenschaft "updateSelector" muss definiert werden.', + '{class} must specify "model" and "{attribute}" or "{name}" property values.' => '{class} muss die Eigenschaften "model" und "{attribute}" oder "{name}" spezifizieren.', 'Are you sure you want to delete this item?' => 'Wollen Sie diesen Eintrag wirklich löschen?', 'Delete' => 'Löschen', 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Zeige Ergebnisse {start}-{end} von {count}.', @@ -34,8 +33,9 @@ 'The button type "{type}" is not supported.' => 'Buttontyp "{type}" wird nicht unterstützt.', 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Die Spalte muss im Format "Name:Typ:Label" angegeben werden, wobei "Typ" und "Label" optional sind.', 'The property "itemView" cannot be empty.' => 'Die Eigenschaft "itemView" darf nicht leer sein.', + 'The property filterSelector should be defined.' => 'Die Eigenschaft "filterSelector" muss definiert werden.', + 'The property updateSelector should be defined.' => 'Die Eigenschaft "updateSelector" muss definiert werden.', 'Total 1 result.|Total {count} results.' => 'Insgesamt 1 Ergebnis.|Insgesamt {count} Ergebnisse.', 'Update' => 'Bearbeiten', 'View' => 'Anzeigen', - '{class} must specify "model" and "attribute" or "name" property values.' => '{class} muss die Eigenschaften "model" und "attribute" oder "name" spezifizieren.', ); diff --git a/framework/messages/es/yii.php b/framework/messages/es/yii.php index 2d1c1d7e3c..62078fdd81 100644 --- a/framework/messages/es/yii.php +++ b/framework/messages/es/yii.php @@ -17,197 +17,187 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'Action class {class} must implement the "run" method.' => 'La clase de acción {class} debe implementar el método "run".', - 'Adding a foreign key constraint to an existing table is not supported by SQLite.' => 'Agregar una restricción de llave foránea no es soportado por SQLite.', - 'Adding a primary key after table has been created is not supported by SQLite.' => 'Agregar una llave primaria después de que la tabla ha sido creada no es soportado por SQLite.', - 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'El alias "{alias}" es inválido. Asegúrese de que apunta a un archivo PHP y el archivo tiene permisos de lectura.', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => 'El alias "{alias}" es inválido. Asegúrese de que apunte a un directorio existente.', - 'Altering a DB column is not supported by SQLite.' => ' Alterar un columna de la BD no es soportado por SQLite.', - 'Application Log' => 'Registro de Aplicación', - 'CDbCacheDependency.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCacheDependency.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia al ID de un componente de aplicación CDbConnection.', - 'CDbCommand::execute() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::execute() falló: {error}. La sentencia SQL ejecutada fue: {sql}.', - 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::{method}() falló: {error}. La sentencia SQL ejecutada fue: {sql}.', - 'CDbTestFixture.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbTestFixture.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia al ID de un componente de aplicación CDbConnection.', - 'CHttpCacheFilter.lastModified contained a value that could not be understood by strtotime()' => 'CHttpCacheFilter.lastModified tuvo un valor que no pudo ser entendido por strtotime()', - 'CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.' => 'CHttpSession.gcProbability "{value}" es inválido. Debe ser un flotante entre 0 y 100.', - 'CMemCache requires PHP {extension} extension to be loaded.' => 'CMemCache requiere la extensión de PHP {extension} esté cargada.', - 'CTypedMap<{type}> can only hold objects of {type} class.' => 'CTypedMap<{type}> sólo puede tener objetos de clase {type}.', - 'CWinCache requires PHP wincache extension to be loaded.' => 'CWinCache requiere que la extensión de PHP esté cargada.', - 'CWinCache user cache is disabled. Please set wincache.ucenabled to On in your php.ini.' => 'La caché de usuario de CWinCache está desactivada. Por favor, cambie wincache.ucenabled a On en su php.ini.', - 'Class name "{class}" does not match class file "{file}".' => 'El nombre de la clase "{class}" no corresponde con la clase del archivo "{file}".', - 'Dropping DB column is not supported by SQLite.' => 'Eliminar columna de la BD no es soportado por SQLite.', - 'Dropping a foreign key constraint is not supported by SQLite.' => 'Eliminar una restricción de llave foránea no es soportado por SQLite.', - 'Extension path "{path}" does not exist.' => 'La ruta "{path}" de la extensión no existe.', - 'Failed to initialize the mcrypt module.' => 'Error al inicializar el módulo mcrypt.', - 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Error al definir unsafe el atributo "{attribute}" de "{class}".', - 'Failed to start session.' => 'Error al iniciar sesión.', - 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'El filtro "{filter}" es inválido. El controlador "{class}" no contiene el método de filtro "filter{filter}".', - 'GD with FreeType or ImageMagick PHP extensions are required.' => 'Las extensiones de PHP GD con FreeType o ImageMagick son requeridas.', - 'In order to use MIME-type validation provided by CFileValidator fileinfo PECL extension should be installed.' => 'Con el fin de usar la validación de tipos MIME provista por CFileValidator, la extensión de PECL fileinfo debe estar instalada.', - 'Invalid expression for CHttpCacheFilter.lastModifiedExpression: The evaluation result "{value}" could not be understood by strtotime()' => 'Expresión inválida para CHttpCacheFilter.lastModifiedExpression: El resultado de la evaluación "{value}" no pudo ser entendido por strtotime()', - 'Invalid operator "{operator}".' => 'Operador inválido "{operator}".', - 'Powered by {yii}.' => 'Potenciado por {yii}.', - 'Property CMaskedTextField.mask cannot be empty.' => 'La propiedad CMaskedTextField.mask no puede estar vacía.', - 'Removing a primary key after table has been created is not supported by SQLite.' => 'Remover una llave primaria después de que una tabla ha sido creada, no es soportado por SQLite.', - 'Renaming a DB column is not supported by SQLite.' => 'Renombrar una columna de la BD no es soportado por SQLite.', - 'The "db" application component must be configured to be a CDbConnection object.' => 'El componente de la aplicación "db" debe estar configurado para ser un objeto CDbConnection.', - 'The "range" property must be specified with a list of values.' => 'La propiedad "range" debe ser especificada con una lista de valores.', - 'The DB query must contain the "from" portion.' => 'La consulta de BD debe contener la parte "from".', - 'The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.' => 'El archivo "{file}" no puede ser subido. Solo los archivos de estos tipo MIME están permitidos: {mimeTypes}.', - 'The format of {attribute} is invalid.' => 'El formato de {attribute} es inválido.', - 'The pattern for day of the week must be "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" or "ccccc".' => 'El patrón para el día de la semana de ser "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" o "ccccc".', - 'The pattern for month must be "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" or "LLLL".' => 'El patrón para mes debe ser "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" o "LLLL".', - 'The requested view "{name}" was not found.' => 'La vista solicitada "{name}" no fue encontrada.', - 'Unable to find "{column}" in table "{table}".' => 'No se ha podido encontrar "{column}" in la tabla "{table}".', - 'Unknown operator "{operator}".' => 'Operador desconocido "{operator}".', - 'Unknown type "{type}".' => 'Tipo desconocido "{type}".', - 'Your request is invalid.' => 'Su solicitud es inválida.', - '{attribute} cannot accept more than {limit} files.' => '{attribute} no puede aceptar más de {limit} archivos.', - '{attribute} is in the list.' => '{attribute} está en la lista.', - '{attribute} must be {value}.' => '{attribute} debe ser {value}.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} no debe ser igual a "{compareValue}".', - '{className} does not support flushValues() functionality.' => '{className} no soporta la funcionalidad flushValues().', - '{class} and its behaviors do not have a method or closure named "{name}".' => '{class} y sus behaviors no tienen un método o closure llamado "{name}".', - '{n} B' => '{n} B', - '{n} GB' => '{n} GB', - '{n} KB' => '{n} KB', - '{n} MB' => '{n} MB', - '{n} TB' => '{n} TB', - '{n} byte|{n} bytes' => '{n} byte|{n} bytes', - '{n} gigabyte|{n} gigabytes' => '{n} gigabyte|{n} gigabytes', - '{n} kilobyte|{n} kilobytes' => '{n} kilobyte|{n} kilobytes', - '{n} megabyte|{n} megabytes' => '{n} megabyte|{n} megabytes', - '{n} terabyte|{n} terabytes' => '{n} terabyte|{n} terabytes', '"{path}" is not a valid directory.' => '"{path}" no es un directorio válido.', '< Previous' => '< Anterior', '<< First' => '<< Primero', + 'Action class {class} must implement the "run" method.' => 'La clase de acción {class} debe implementar el método "run".', 'Active Record requires a "db" CDbConnection application component.' => 'Active Record requiere un componente de aplicación "db" del tipo CDbConnection.', - 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'Active record "{class}" contiene una configuración de relación inválida "{relation}". La misma debe especificar el tipo de relación, la clase active record relacionada y la clave foranea.', - 'Active record "{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.' => 'Active record "{class}" esta intentando de seleccionar una columna inválida "{column}". Nota: La columna puede existir en la base o ser una expresion con alias.', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'Alias "{alias}" es inválido. Verifique que el mismo apunta a un directorio o archivo exisitente.', - 'Application base path "{path}" is not a valid directory.' => 'Ruta base de la aplicación "{path}" no es un directorio válido.', - 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.' => 'Ruta de runtime de aplicación "{path}" es inválida. Verifique que sea un directorio con permisos de escritura por el proceso que corre el servidor Web.', - 'Authorization item "{item}" has already been assigned to user "{user}".' => 'Elemento de autorización "{item}" ha sido asignado al usuario "{user}".', + 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'La clase Active Record "{class}" contiene una configuración inválida para la relación "{relation}". La misma debe especificar el tipo de relación, la clase active record relacionada y la clave foránea.', + 'Active record "{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.' => 'La clase Active record "{class}" esta intentando seleccionar una columna inválida "{column}". Nota: la columna debe existir en la tabla o ser una expresion con alias.', + 'Adding a foreign key constraint to an existing table is not supported by SQLite.' => 'Agregar una restricción de clave foránea a una tabla existente no es soportado por SQLite.', + 'Adding a primary key after table has been created is not supported by SQLite.' => 'Agregar una clave primaria después de que la tabla ha sido creada no es soportado por SQLite.', + 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'El alias "{alias}" es inválido. Asegúrese de que apunta a un archivo PHP existente y que el archivo tiene permisos de lectura.', + 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'El alias "{alias}" es inválido. Asegúrese de que apunta a un directorio o archivo existente.', + 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => 'El alias "{alias}" es inválido. Asegúrese de que apunta a un directorio existente.', + 'Altering a DB column is not supported by SQLite.' => 'Alterar una columna de la BD no esta soportado por SQLite.', + 'Application Log' => 'Registro de Aplicación', + 'Application base path "{path}" is not a valid directory.' => 'La ruta base de la aplicación "{path}" no es un directorio válido.', + 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.' => 'La ruta de tiempo de ejecución de la aplicación "{path}" es inválida. Por favor asegúrese de que sea un directorio con permisos de escritura por el proceso que corre el servidor Web.', + 'Authorization item "{item}" has already been assigned to user "{user}".' => 'El elemento de autorización "{item}" ya ha sido asignado con anterioridad al usuario "{user}".', 'Base path "{path}" is not a valid directory.' => 'La ruta base "{path}" no es un directorio válido', 'CApcCache requires PHP apc extension to be loaded.' => 'CApcCache requiere que la extensión apc de PHP se encuentre cargada.', - 'CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.' => 'CAssetManager.basePath "{path}" es inválido. Verifique que el directorio exista y tenga permisos de escritura por el proceso que corre el servidor Web.', - 'CCacheHttpSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.' => 'CCacheHttpSession.cacheID es inválido. Asegurese que "{id}" refiere a un componente de aplicación de cache válido.', - 'CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.' => 'CCaptchaValidator.action "{id}" es inválido. No se há podido encontrar dicha acción en el controlador actual.', - 'CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbAuthManager.connectionID "{id}" es inválido. Asegurese que se refiere a un ID de un componente de aplicación CDbConnection.', - 'CDbCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCache.connectionID "{id}" es inválido. Asegurese que refiera a un ID de un componente de aplicación CDbConnection.', + 'CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.' => 'CAssetManager.basePath "{path}" es inválido. Asegúrese que el directorio exista y tenga permisos de escritura por el proceso que corre el servidor Web.', + 'CCacheHttpSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.' => 'CCacheHttpSession.cacheID es inválido. Por favor, asegúrese que "{id}" hace referencia a un componente de aplicación de cache válido.', + 'CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.' => 'CCaptchaValidator.action "{id}" es inválido. No se ha podido encontrar dicha acción en el controlador actual.', + 'CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbAuthManager.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia a un ID de un componente de aplicación CDbConnection.', + 'CDbCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCache.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia a un ID de un componente de aplicación CDbConnection.', + 'CDbCacheDependency.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCacheDependency.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia al ID de un componente de aplicación CDbConnection.', 'CDbCacheDependency.sql cannot be empty.' => 'CDbCacheDependency.sql no puede ser vacío.', 'CDbCommand failed to execute the SQL statement: {error}' => 'CDbCommand falló al ejecutar la sentencia SQL: {error}', 'CDbCommand failed to prepare the SQL statement: {error}' => 'CDbCommand falló al preparar la sentencia SQL: {error}', + 'CDbCommand::execute() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::execute() falló: {error}. La sentencia SQL ejecutada fue: {sql}.', + 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.' => 'CDbCommand::{method}() falló: {error}. La sentencia SQL ejecutada fue: {sql}.', 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection no soporta la lectura del esquema para la base de datos {driver}.', - 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader no puede volver atras ya que es un lector de un avance únicamente.', - 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbHttpSession.connectionID "{id}" es inválido. Asegurese que refiera a un ID de un componente de aplicación CDbConnection', - 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'CDbLogRoute.connectionID "{id}" no refiere a un componente de aplicación CDbConnection válido.', - 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'CDbMessageSource.connectionID es inválido. Asegurese que "{id}" refiera a un componente de aplicación de base de datos válido.', - 'CDbTransaction is inactive and cannot perform commit or roll back operations.' => 'CDbTransaction se encuentra inactiva y no puede realizar la operación commit ni roll back.', + 'CDbConnection failed to open the DB connection.' => 'CDbConnection falló al abrir la conexión con la BD', + 'CDbConnection is unable to find PDO class "{className}". Make sure PDO is installed correctly.' => 'CDbConnection no puede encontrar la clase PDO "{className}". Asegúrate de que PDO esté instalado correctamente', + 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader no puede rebobinar. Se trata de un lector de solo avance.', + 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbHttpSession.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia a un ID de un componente de aplicación CDbConnection', + 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'CDbLogRoute.connectionID "{id}" no hace referencia a un componente de aplicación CDbConnection válido.', + 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'CDbMessageSource.connectionID es inválido. Por favor, asegúrese que "{id}" hace referencia a un componente de aplicación de base de datos válido.', + 'CDbTestFixture.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbTestFixture.connectionID "{id}" es inválido. Por favor, asegúrese de que hace referencia al ID de un componente de aplicación CDbConnection.', + 'CDbTransaction is inactive and cannot perform commit or roll back operations.' => 'CDbTransaction se encuentra inactiva y no puede realizar las operacines commit ni roll back.', 'CDirectoryCacheDependency.directory cannot be empty.' => 'CDirectoryCacheDependency.directory no puede ser vacío.', - 'CEAcceleratorCache requires PHP eAccelerator extension to be loaded, enabled or compiled with the "--with-eaccelerator-shared-memory" option.' => 'CEAcceleratorCache requiere la extensión eAccelerator de PHP para ser cargada, activada o cargada con la opción "--with-eaccelerator-shared-memory".', + 'CEAcceleratorCache requires PHP eAccelerator extension to be loaded, enabled or compiled with the "--with-eaccelerator-shared-memory" option.' => 'CEAcceleratorCache requiere que la extensión eAccelerator de PHP esté cargada, activada o compilada con la opción "--with-eaccelerator-shared-memory".', 'CFileCacheDependency.fileName cannot be empty.' => 'CFileCacheDependency.fileName no puede ser vacío.', - 'CFileLogRoute.logPath "{path}" does not point to a valid directory. Make sure the directory exists and is writable by the Web server process.' => 'CFileLogRoute.logPath "{path}" no apunta a un directorio válido. Verifique que el directorio exista y tenga permisos de escritura por el proceso que corre el servidor Web.', - 'CFilterChain can only take objects implementing the IFilter interface.' => 'CFilterChain solamente puede tomar objetos que implementen la interface IFilter.', + 'CFileLogRoute.logPath "{path}" does not point to a valid directory. Make sure the directory exists and is writable by the Web server process.' => 'CFileLogRoute.logPath "{path}" no apunta a un directorio válido. Asegúrese que el directorio exista y tenga permisos de escritura por el proceso que corre el servidor Web.', + 'CFilterChain can only take objects implementing the IFilter interface.' => 'CFilterChain solamente puede tomar objetos que implementen la interfaz IFilter.', 'CFlexWidget.baseUrl cannot be empty.' => 'CFlexWidget.baseUrl no puede ser vacío.', 'CFlexWidget.name cannot be empty.' => 'CFlexWidget.name no puede ser vacío.', 'CGlobalStateCacheDependency.stateName cannot be empty.' => 'CGlobalStateCacheDependency.stateName no puede ser vacío.', + 'CHttpCacheFilter.lastModified contained a value that could not be understood by strtotime()' => 'CHttpCacheFilter.lastModified tuvo un valor que no pudo ser entendido por strtotime()', 'CHttpCookieCollection can only hold CHttpCookie objects.' => 'CHttpCookieCollection solo puede contener objetos CHttpCookie.', - 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest no puede determinar la URL de su script de entrada.', + 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest no puede determinar la URL del script de entrada.', 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest no puede determinar la información de la ruta de la solicitud.', - 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest no puede derminar la URI solicitada', - 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'CHttpSession.cookieMode solo puede ser "none", "allow" ó "only".', + 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest no puede derminar la URI de la solicitud.', + 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'CHttpSession.cookieMode solo puede ser "none", "allow" u "only".', + 'CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.' => 'CHttpSession.gcProbability "{value}" es inválido. Debe ser un flotante entre 0 y 100.', 'CHttpSession.savePath "{path}" is not a valid directory.' => 'CHttpSession.savePath "{path}" no es un directorio válido.', + 'CMemCache requires PHP {extension} extension to be loaded.' => 'CMemCache requiere que la extensión de PHP {extension} esté cargada.', 'CMemCache server configuration must be an array.' => 'La configuración del servidor CMemCache debe ser un array.', 'CMemCache server configuration must have "host" value.' => 'La configuración del servidor CMemCache debe contener un "host".', - 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute ha encontrado un bloque de código "{token}" desalineado. Asegurese que las llamadas a Yii::beginProfile() y a Yii::endProfile() esten correctamente anidadas.', - 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".' => 'CProfileLogRoute.report "{report}" es inválido. Los valores validos son "summary" y "callstack".', - 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager requiere que la extensión mcrypt de PHP sea cargada para utilizar la opción de encriptación de datos.', + 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute ha encontrado un bloque de código "{token}" no coincidente. Asegúrese que las llamadas a Yii::beginProfile() y a Yii::endProfile() esten correctamente anidadas.', + 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".' => 'CProfileLogRoute.report "{report}" es inválido. Los valores válidos son "summary" y "callstack".', + 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager requiere que la extensión mcrypt de PHP esté cargada para utilizar la característica de encriptación de datos.', 'CSecurityManager.encryptionKey cannot be empty.' => 'CSecurityManager.encryptionKey no puede ser vacío.', 'CSecurityManager.validationKey cannot be empty.' => 'CSecurityManager.validationKey no puede ser vacío.', + 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() no puede generar una cadena aleatoria en el entorno actual.', 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> solo puede contener objetos de la clase {type}.', + 'CTypedMap<{type}> can only hold objects of {type} class.' => 'CTypedMap<{type}> solo puede tener objetos de clase {type}.', 'CUrlManager.UrlFormat must be either "path" or "get".' => 'CUrlManager.UrlFormat debe ser "path" o "get".', - 'CXCache requires PHP XCache extension to be loaded.' => 'CXCache requiere la extensión XCache de PHP para ser cargado', - 'CZendDataCache requires PHP Zend Data Cache extension to be loaded.' => 'CZendDataCache requiere la extensión PHP Zend Data Cache para poder ser cargada.', - 'Cannot add "{child}" as a child of "{name}". A loop has been detected.' => 'No se puede agregar "{child}" como hijo de "{name}". Un ciclo infinito se há detectado.', - 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'No se puede agregar "{child}" como hijo de "{parent}". Un ciclo infinito se há detectado.', + 'CWinCache requires PHP wincache extension to be loaded.' => 'CWinCache requiere que la extensión wincache de PHP esté cargada.', + 'CWinCache user cache is disabled. Please set wincache.ucenabled to On in your php.ini.' => 'La caché de usuario de CWinCache está desactivada. Por favor, cambie wincache.ucenabled a On en su php.ini.', + 'CXCache requires PHP XCache extension to be loaded.' => 'CXCache requiere que la extensión XCache de PHP esté cargada.', + 'CZendDataCache requires PHP Zend Data Cache extension to be loaded.' => 'CZendDataCache requiere que la extensión PHP Zend Data Cache esté cargada.', + 'Cannot add "{child}" as a child of "{name}". A loop has been detected.' => 'No se puede agregar "{child}" como hijo de "{name}". Un ciclo infinito se ha detectado.', + 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'No se puede agregar "{child}" como hijo de "{parent}". Un ciclo infinito se ha detectado.', 'Cannot add "{name}" as a child of itself.' => 'No se puede agregar "{name}" como hijo de sí mismo.', - 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'No se le puede agregar un elemento del tipo "{child}" a otro del tipo "{parent}".', + 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'No se puede agregar un elemento del tipo "{child}" a un elemento del tipo "{parent}".', + 'Cannot hash a password that is empty or not a string.' => 'No se puede generar un hash de un password que es vacío o que no es una cadena', + 'Class name "{class}" does not match class file "{file}".' => 'El nombre de la clase "{class}" no coincide con el archivo de la clase "{file}".', 'Column name must be either a string or an array.' => 'El nombre de la columna debe ser una cadena o un array.', - 'Either "{parent}" or "{child}" does not exist.' => '"{parent}" o "{child}" es inexistente', + 'Dropping DB column is not supported by SQLite.' => 'La eliminación de columnas de BD no es soportado por SQLite.', + 'Dropping a foreign key constraint is not supported by SQLite.' => 'La eliminación de una restricción de clave foránea no es soportado por SQLite.', + 'Either "{parent}" or "{child}" does not exist.' => 'No existe "{parent}" ni "{child}"', 'Error: Table "{table}" does not have a primary key.' => 'Error: Tabla "{table}" no tiene una clave primaria.', 'Error: Table "{table}" has a composite primary key which is not supported by crud command.' => 'Error: Tabla "{table}" tiene una clave primaria compuesta que no es soportada por el comando crud.', 'Event "{class}.{event}" is attached with an invalid handler "{handler}".' => 'Evento "{class}"."{event}" tiene asociado un manejador "{handler}" inválido.', 'Event "{class}.{event}" is not defined.' => 'Evento "{class}"."{event}" no se encuentra definido.', - 'Failed to write the uploaded file "{file}" to disk.' => 'Error al escribir el archivo subido "{file}" al disco.', - 'File upload was stopped by extension.' => 'El upload de archivo fue terminado debido a su extensión.', + 'Extension path "{path}" does not exist.' => 'La ruta "{path}" de la extensión no existe.', + 'Failed to initialize the mcrypt module.' => 'Error al inicializar el módulo mcrypt.', + 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Error al establecer unsafe al atributo "{attribute}" de "{class}".', + 'Failed to start session.' => 'Error al iniciar sesión.', + 'Failed to write the uploaded file "{file}" to disk.' => 'No se pudo escribir el archivo subido "{file}" en el disco', + 'File upload was stopped by extension.' => 'La subida del archivo fue detenida debido a una extensión PHP.', + 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'El filtro "{filter}" es inválido. El controlador "{class}" no contiene el método de filtro "filter{filter}".', + 'GD with FreeType or ImageMagick PHP extensions are required.' => 'Las extensiones de PHP GD con FreeType o ImageMagick son requeridas.', 'Get a new code' => 'Obtenga un nuevo código', 'Go to page: ' => 'Ir a página: ', + 'In order to use MIME-type validation provided by CFileValidator fileinfo PECL extension should be installed.' => 'Con el fin de usar la validación de tipos MIME provista por CFileValidator, la extensión de PECL fileinfo debe estar instalada.', + 'Internal error while generating hash.' => 'Hubo un error interno al generar el hash', 'Invalid MO file revision: {revision}.' => 'Revisión de archivo MO inválido: {revision}.', 'Invalid MO file: {file} (magic: {magic}).' => 'Archivo MO inválido: {file} (magic: {magic}).', - 'Invalid enumerable value "{value}". Please make sure it is among ({enum}).' => 'Valor de enumerador inválido "{value}". Asegurese que este entre ({enum}).', + 'Invalid enumerable value "{value}". Please make sure it is among ({enum}).' => 'Valor de enumerador inválido "{value}". Asegúrese que este entre ({enum}).', + 'Invalid expression for CHttpCacheFilter.lastModifiedExpression: The evaluation result "{value}" could not be understood by strtotime()' => 'Expresión inválida para CHttpCacheFilter.lastModifiedExpression: El resultado de la evaluación "{value}" no pudo ser entendido por strtotime()', + 'Invalid operator "{operator}".' => 'Operador inválido "{operator}".', 'Last >>' => 'Último >>', - 'List data must be an array or an object implementing Traversable.' => 'Los datos de la lista deben ser un array o un objeto que implemento Traversable.', - 'List index "{index}" is out of bound.' => 'Indice de la lista "{index}" esta fuera del limite.', - 'Login Required' => 'Iniciar sesión requerido.', - 'Map data must be an array or an object implementing Traversable.' => 'Los datos del mapa deben ser un array o un objeto que implemento Traversable', - 'Missing the temporary folder to store the uploaded file "{file}".' => 'La carpeta temoporaria para guardar el archivo subido "{file}" no se encuentra.', + 'List data must be an array or an object implementing Traversable.' => 'Los datos de la lista deben ser un array o un objeto que implemente Traversable.', + 'List index "{index}" is out of bound.' => 'El índice de la lista "{index}" está fuera del límite.', + 'Login Required' => 'Inicio de sesión necesario', + 'Map data must be an array or an object implementing Traversable.' => 'Los datos del mapa deben ser un array o un objeto que implementa Traversable', + 'Missing the temporary folder to store the uploaded file "{file}".' => 'No se encuentra la carpeta temoporaria para almacenar el archivo subido "{file}".', 'Next >' => 'Siguiente >', + 'No' => 'No', 'No columns are being updated for table "{table}".' => 'No se actualizó ninguna columna para la tabla "{table}".', 'No counter columns are being updated for table "{table}".' => 'Ningun contador de columnas ha sido actualizado para la tabla "{table}".', 'Object configuration must be an array containing a "class" element.' => 'La configuración del objeto debe ser un array conteniendo un elemento "class".', + 'Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.' => 'Solo los algoritmos de hash SHA1 y MD5 son soportados cuando se usa PHP 5.1.1 o menor', 'Please fix the following input errors:' => 'Por favor corrija los siguientes errores de ingreso:', - 'Property "{class}.{property}" is not defined.' => 'Propiedad "{class}"."{property}" no se encuentra definida.', - 'Property "{class}.{property}" is read only.' => 'Propiedad "{class}"."{property}" es de solo lectura..', - 'Queue data must be an array or an object implementing Traversable.' => 'Los datos de la cola deben ser un array o un objeto que implemento Traversable', + 'Powered by {yii}.' => 'Potenciado por {yii}.', + 'Property "{class}.{property}" is not defined.' => 'La propiedad "{class}"."{property}" no se encuentra definida.', + 'Property "{class}.{property}" is read only.' => 'La propiedad "{class}"."{property}" es de solo lectura..', + 'Property CMaskedTextField.mask cannot be empty.' => 'La propiedad CMaskedTextField.mask no puede estar vacía.', + 'Queue data must be an array or an object implementing Traversable.' => 'Los datos de la cola deben ser un array o un objeto que implementa Traversable', 'Relation "{name}" is not defined in active record class "{class}".' => 'La relación "{name}" no se encuentra definida en la clase active record "{class}".', - 'Stack data must be an array or an object implementing Traversable.' => 'Los datos de la pila deben ser un array o un objeto que implemento Traversable', + 'Removing a primary key after table has been created is not supported by SQLite.' => 'Remover una clave primaria después de que una tabla ha sido creada, no es soportado por SQLite.', + 'Renaming a DB column is not supported by SQLite.' => 'Renombrar una columna de la BD no es soportado por SQLite.', + 'Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".' => 'Las opciones de Script de HTML no son permitidas para "CClientScript::POS_LOAD" ni "CClientScript::POS_READY".', + 'Stack data must be an array or an object implementing Traversable.' => 'Los datos de la pila deben ser un array o un objeto que implementa Traversable', 'Table "{table}" does not exist.' => 'La tabla "{table}" no existe.', - 'Table "{table}" does not have a column named "{column}".' => 'Tabla "{table}" no contiene la columna "{column}".', + 'Table "{table}" does not have a column named "{column}".' => 'Tabla "{table}" no contiene la columna con nombre "{column}".', + 'The "db" application component must be configured to be a CDbConnection object.' => 'El componente de la aplicación "db" debe estar configurado para ser un objeto CDbConnection.', 'The "filter" property must be specified with a valid callback.' => 'La propiedad "filter" debe ser especificada con un callback válido.', + 'The "forceCopy" and "linkAssets" cannot be both true.' => 'Los parámetros "forceCopy" y "linkAssets" no pueden ser a la vez verdaderos', 'The "pattern" property must be specified with a valid regular expression.' => 'La propiedad "pattern" debe ser especificada con una expresión regular válida.', - 'The CSRF token could not be verified.' => 'Su token CSRF no puede ser verificado.', + 'The "range" property must be specified with a list of values.' => 'La propiedad "range" debe ser especificada con una lista de valores.', + 'The $converter argument must be a valid callback or null.' => 'El argumento $converter debe tener un callback válido o nulo', + 'The CSRF token could not be verified.' => 'El token CSRF no puede ser verificado.', + 'The DB query must contain the "from" portion.' => 'La consulta de BD debe contener la parte "from".', 'The STAT relation "{name}" cannot have child relations.' => 'La relación STAT "{name}" no puede tener relaciones hijas.', 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'El patrón de URL "{pattern}" para la ruta "{route}" no es una expresión regular válida.', 'The active record cannot be deleted because it is new.' => 'El active record no puede ser eliminado porque es nuevo.', 'The active record cannot be inserted to database because it is not new.' => 'El active record no puede ser insertado a la base de datos porque no es nuevo.', 'The active record cannot be updated because it is new.' => 'El active record no puede ser actualizado porque es nuevo.', 'The asset "{asset}" to be published does not exist.' => 'El asset "{asset} a ser publicado no existe.', - 'The command path "{path}" is not a valid directory.' => 'La ruta de comando "{path}" no es un directorio válido.', + 'The command path "{path}" is not a valid directory.' => 'La ruta del comando "{path}" no es un directorio válido.', 'The controller path "{path}" is not a valid directory.' => 'La ruta del controlador "{path}" no es un directorio válido.', + 'The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.' => 'El archivo "{file}" no puede ser subido. Solo los archivos de estos tipo MIME están permitidos: {mimeTypes}.', 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.' => 'El archivo "{file}" no puede ser subido. Solo los archivos con estas extensiones son permitidos: {extensions}.', 'The file "{file}" is too large. Its size cannot exceed {limit} bytes.' => 'El archivo "{file}" es muy grande. Su tamaño no puede exceder {limit} bytes.', 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.' => 'El archivo "{file}" es muy chico. Su tamaño no puede ser menor que {limit} bytes.', 'The file "{file}" was only partially uploaded.' => 'El archivo "{file}" ha sido subido parcialmente.', 'The first element in a filter configuration must be the filter class.' => 'El primer elemento en la configuración de un filtro debe ser la clase del filtro.', + 'The format of {attribute} is invalid.' => 'El formato de {attribute} es inválido.', 'The item "{name}" does not exist.' => 'El elemento "{name}" es inexistente.', 'The item "{parent}" already has a child "{child}".' => 'El elemento "{parent}" ya contiene un hijo "{child}".', - 'The layout path "{path}" is not a valid directory.' => 'La ruta de esquema "{path}" no es un directorio válido.', + 'The layout path "{path}" is not a valid directory.' => 'La ruta del diseño "{path}" no es un directorio válido.', 'The list is read only.' => 'La lista es de solo lectura', 'The map is read only.' => 'El mapa es de solo lectura', 'The module path "{path}" is not a valid directory.' => 'La ruta del módulo "{path}" no es un directorio válido.', - 'The pattern for 12 hour format must be "h" or "hh".' => 'El patrón para hora en formato 12 debe ser "h" ó "hh".', - 'The pattern for 24 hour format must be "H" or "HH".' => 'El patrón para hora en formato 24 debe ser "H" ó "HH".', + 'The pattern for 12 hour format must be "h" or "hh".' => 'El patrón para hora en formato 12 debe ser "h" o "hh".', + 'The pattern for 24 hour format must be "H" or "HH".' => 'El patrón para hora en formato 24 debe ser "H" o "HH".', 'The pattern for AM/PM marker must be "a".' => 'El patrón para el marcador AM/PM debe ser "a".', - 'The pattern for day in month must be "F".' => 'El patrón para día del mes debe ser "F".', - 'The pattern for day in year must be "D", "DD" or "DDD".' => 'El patrón para día del año debe ser "D", "DD", "DDD".', - 'The pattern for day of the month must be "d" or "dd".' => 'El patrón para día debe ser "d" ó "dd".', - 'The pattern for era must be "G", "GG", "GGG", "GGGG" or "GGGGG".' => 'El patrón para era debe ser "G", "GG", "GGG", "GGGG" ó "GGGGG".', - 'The pattern for hour in AM/PM must be "K" or "KK".' => 'El patrón para hora en AM/PM debe ser "K" ó "KK".', - 'The pattern for hour in day must be "k" or "kk".' => 'El patrón para hora del día debe ser "k" ó "kk".', - 'The pattern for minutes must be "m" or "mm".' => 'El patrón para minutos debe ser "m" ó "mm".', - 'The pattern for seconds must be "s" or "ss".' => 'El patrón para segundos debe ser "s" ó "ss".', - 'The pattern for time zone must be "z" or "v".' => 'El patrón para zona horaria debe ser "z" ó "v".', + 'The pattern for day in month must be "F".' => 'El patrón para día en el mes debe ser "F".', + 'The pattern for day in year must be "D", "DD" or "DDD".' => 'El patrón para día en el año debe ser "D", "DD", "DDD".', + 'The pattern for day of the month must be "d" or "dd".' => 'El patrón para día del mes debe ser "d" o "dd".', + 'The pattern for day of the week must be "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" or "ccccc".' => 'El patrón para el día de la semana de ser "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" o "ccccc".', + 'The pattern for era must be "G", "GG", "GGG", "GGGG" or "GGGGG".' => 'El patrón para era debe ser "G", "GG", "GGG", "GGGG" o "GGGGG".', + 'The pattern for hour in AM/PM must be "K" or "KK".' => 'El patrón para hora en AM/PM debe ser "K" o "KK".', + 'The pattern for hour in day must be "k" or "kk".' => 'El patrón para hora del día debe ser "k" o "kk".', + 'The pattern for minutes must be "m" or "mm".' => 'El patrón para minutos debe ser "m" o "mm".', + 'The pattern for month must be "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" or "LLLL".' => 'El patrón para mes debe ser "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" o "LLLL".', + 'The pattern for seconds must be "s" or "ss".' => 'El patrón para segundos debe ser "s" o "ss".', + 'The pattern for time zone must be "z" or "v".' => 'El patrón para zona horaria debe ser "z" o "v".', 'The pattern for week in month must be "W".' => 'El patron para semana del mes debe ser "W".', 'The pattern for week in year must be "w".' => 'El patrón para semana del año debe ser "w".', 'The queue is empty.' => 'La cola está vacía', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relación "{relation}" en la clase de registro activo "{class}" está especificada correctamente. La tabla de unión o join "{joinTable}" dada en la llave foránea no se encuentra en la base de datos.', - 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relación "{relation}" en la clase active record "{class}" no se encuentra especificada correctamente: La tabla de junta (join table) "{joinTable}" dada no se encontro en la base de datos.', - 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'La relación "{relation}" en la clase de registro activo "{class}" es especificada con una llave foránea "{key}" que no apunta a la tabla padre "{table}".', - 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'La relación "{relation}" en la clase active record "{class}" se encuentra especificada con una clave foranea incompleta. La clave foranea debe consistir de las columnas que referencian la junta de tablas.', - 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".' => 'La relación "{relation}" en la clase de registro activo "{class}" es especificada con una llave foránea inválida "{key}". No hay tal columna en la tabla "{table}".', - 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "{table}".' => 'La relación "{relation}" en la clase de registro activo "{class}" es especificada con una llave foránea inválida. Las columnas en la llave deben coincidir con las llaves primarias de la tabla "{table}".', + 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relación "{relation}" en la clase active record "{class}" no se encuentra especificada correctamente. La tabla de combinacion "{joinTable}" dada en la clave foránea no se encuentra en la base de datos.', + 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relación "{relation}" en la clase active record "{class}" no se encuentra especificada correctamente: la tabla de combinacion "{joinTable}" dada en la clave foránea no se encuentra en la base de datos.', + 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'La relación "{relation}" en la clase active record "{class}" es especificada con una clave foránea "{key}" que no apunta a la tabla padre "{table}".', + 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'La relación "{relation}" en la clase active record "{class}" se encuentra especificada con una clave foranea incompleta. La clave foranea debe consistir de las columnas que referencian la combinación de tablas.', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".' => 'La relación "{relation}" en la clase active record "{class}" es especificada con una clave foránea inválida "{key}". No hay tal columna en la tabla "{table}".', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "{table}".' => 'La relación "{relation}" en la clase active record "{class}" es especificada con una clave foránea inválida. Las columnas en la clave deben coincidir con las claves primarias de la tabla "{table}".', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".' => 'La relación "{relation}" en la clase active record "{class}" se encuentra especificada con una clave foranea inválida. El formato de la clave foranea debe ser "joinTable(fk1,fk2,...)".', + 'The requested view "{name}" was not found.' => 'La vista solicitada "{name}" no fue encontrada.', 'The stack is empty.' => 'La pila está vacía', 'The system is unable to find the requested action "{action}".' => 'El sistema no ha podido encontrar la acción "{action}" solicitada.', 'The system view path "{path}" is not a valid directory.' => 'La ruta de vistas de sistema "{path}" no es un directorio válido.', @@ -216,27 +206,35 @@ 'The verification code is incorrect.' => 'El código de verificación es incorrecto.', 'The view path "{path}" is not a valid directory.' => 'La ruta de la vista "{path}" no es un directorio válido.', 'Theme directory "{directory}" does not exist.' => 'El directorio de tema "{directory}" no existe.', - 'This content requires the Adobe Flash Player.' => 'Este contenido requiere el Adobe Flash Player.', + 'This content requires the Adobe Flash Player.' => 'Este contenido requiere el Reproductor de Adobe Flash.', 'Unable to add an item whose name is the same as an existing item.' => 'No se puede agregar un elemento cuyo nombre es el mismo que el de un elemento existente.', 'Unable to change the item name. The name "{name}" is already used by another item.' => 'No se puede modificar el nombre del elemento. El nombre "{name}" ya se encuentra utilizado por otro elemento.', - 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'No se ha podido crear el archivo de estado de aplicación "{file}". Asegurese que el directorio que contiene el archivo exista y sea un directorio con permisos de escritura por el proceso que corre el servidor Web.', - 'Unable to lock file "{file}" for reading.' => 'No se há podido bloquear el archivo "{file}" para lectura.', + 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'No se ha podido crear el archivo de estado de aplicación "{file}". Asegúrese que el directorio que contiene el archivo exista y sea un directorio con permisos de escritura por el proceso que corre el servidor Web.', + 'Unable to find "{column}" in table "{table}".' => 'No se ha podido encontrar "{column}" en la tabla "{table}".', + 'Unable to generate random string.' => 'No se puede generar una cadena aleatoria.', + 'Unable to lock file "{file}" for reading.' => 'No se ha podido bloquear el archivo "{file}" para lectura.', 'Unable to lock file "{file}" for writing.' => 'No se ha podido bloquear el archivo "{file}" para escritura.', 'Unable to read file "{file}".' => 'No se ha podido leer el archivo "{file}".', - 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'Imposible de replicar la acción "{object}.{method}". El metodo es inexistente.', + 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'No se puede reproducir la acción "{object}.{method}". El método es inexistente.', 'Unable to resolve the request "{route}".' => 'No es posible resolver la solicitud "{route}"', 'Unable to write file "{file}".' => 'No se ha podido escribir el archivo "{file}".', 'Unknown authorization item "{name}".' => 'Elemento de autorización "{name}" desconocido.', - 'Unrecognized locale "{locale}".' => 'Localizacion no reconocida "{locale}".', + 'Unknown operator "{operator}".' => 'Operador desconocido "{operator}".', + 'Unknown type "{type}".' => 'Tipo desconocido "{type}".', + 'Unrecognized locale "{locale}".' => 'Configuración regional no reconocida "{locale}".', 'View file "{file}" does not exist.' => 'El archivo de vista "{view}" no existe.', - 'Yii application can only be created once.' => 'Solo se puede crear una aplicación Yii.', + 'Yes' => 'Si', + 'Yii application can only be created once.' => 'La aplicación Yii puede ser creada solo una vez.', 'You are not authorized to perform this action.' => 'Usted no se encuentra autorizado a realizar esta acción.', + 'Your request is invalid.' => 'Su solicitud es inválida.', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" ya ha sido tomado.', '{attribute} "{value}" is invalid.' => '{attribute} "{value}" es inválido.', + '{attribute} cannot accept more than {limit} files.' => '{attribute} no puede aceptar más de {limit} archivos.', '{attribute} cannot be blank.' => '{attribute} no puede ser nulo.', + '{attribute} is in the list.' => '{attribute} está en la lista.', '{attribute} is invalid.' => '{attribute} es inválido.', '{attribute} is not a valid URL.' => '{attribute} no es una URL válida.', - '{attribute} is not a valid email address.' => '{attribute} no es un email válido.', + '{attribute} is not a valid email address.' => '{attribute} no es una dirección de email válida.', '{attribute} is not in the list.' => '{attribute} no se encuentra en la lista.', '{attribute} is of the wrong length (should be {length} characters).' => '{attribute} tiene un largo incorrecto (debe ser de {length} caracteres)', '{attribute} is too big (maximum is {max}).' => '{attribute} es muy grande (el máximo es {max}).', @@ -252,18 +250,36 @@ '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} debe ser menor o igual que "{compareValue}".', '{attribute} must be repeated exactly.' => '{attribute} debe ser repetido exactamente.', '{attribute} must be {type}.' => '{attribute} debe ser {type}.', + '{attribute} must be {value}.' => '{attribute} debe ser {value}.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} no debe ser igual a "{compareValue}".', '{className} does not support add() functionality.' => '{className} no soporta la funcionalidad add().', '{className} does not support delete() functionality.' => '{className} no soporta la funcionalidad delete().', + '{className} does not support flushValues() functionality.' => '{className} no soporta la funcionalidad flushValues().', '{className} does not support get() functionality.' => '{className} no soporta la funcionalidad get().', '{className} does not support set() functionality.' => '{className} no soporta la funcionalidad set().', + '{class} and its behaviors do not have a method or closure named "{name}".' => '{class} y sus behaviors no tienen un método o closure llamado "{name}".', '{class} does not have relation "{name}".' => '{class} no tiene la relación "{name}".', '{class} does not support fetching all table names.' => '{class} no soporta traer todos los nombres de las tablas.', '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} tiene una regla de validación inválida. La regla se debe especificar attributos para ser validados y el nombre de su validador.', '{class} must specify "model" and "attribute" or "name" property values.' => '{class} debe especificar los valores de propiedad "model" y "attribute" o "name".', - '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin debe ser asignado verdadero para poder utilizar la autenticación basada en cookies.', - '{class}::authenticate() must be implemented.' => '{class}::authenticate() debe ser implementad.', + '{class} requires the Blowfish option of the PHP crypt() function. This system does not have it.' => '{class} requiere la opción Blowfish de la función PHP crypt(). Este sistema no la tiene.', + '{class} requires the PHP crypt() function. This system does not have it.' => '{class} requiere la función PHP crypt(). Este sistema no la tiene.', + '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin debe ser asignado true para poder utilizar la autenticación basada en cookies.', + '{class}::$cost must be a number.' => '{class}::$cost debe ser un número.', + '{class}::$cost must be between 4 and 31.' => '{class}::$cost debe estar entre 4 y 31.', + '{class}::authenticate() must be implemented.' => '{class}::authenticate() debe ser implementada.', '{controller} cannot find the requested view "{view}".' => '{controller} no ha podido encontrar la vista "{view}" solicitada.', - '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} contiene etiquetas de widget en la vista "{view}" anidados incorrectamente. {widget} widget no contiene la llamada a endWidget().', + '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} contiene etiquetas de widget en la vista "{view}" anidadas incorrectamente. {widget} widget no contiene la llamada a endWidget().', '{controller} has an extra endWidget({id}) call in its view.' => '{controller} tiene una llamada extra a endWidget({id}) en su vista.', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} TB' => '{n} TB', + '{n} byte|{n} bytes' => '{n} byte|{n} bytes', + '{n} gigabyte|{n} gigabytes' => '{n} gigabyte|{n} gigabytes', + '{n} kilobyte|{n} kilobytes' => '{n} kilobyte|{n} kilobytes', + '{n} megabyte|{n} megabytes' => '{n} megabyte|{n} megabytes', + '{n} terabyte|{n} terabytes' => '{n} terabyte|{n} terabytes', '{widget} cannot find the view "{view}".' => '{widget} no ha podido encontrar la vista "{view}".', ); diff --git a/framework/messages/es/zii.php b/framework/messages/es/zii.php index 0207f73e2b..15984ad7eb 100644 --- a/framework/messages/es/zii.php +++ b/framework/messages/es/zii.php @@ -17,25 +17,25 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'Home' => 'Inicio', - 'The button type "{type}" is not supported.' => 'El tipo de botón "{type}" no es soportado.', - 'The property filterSelector should be defined.' => 'La propiedad filterSelector debería estar definida.', - 'The property updateSelector should be defined.' => 'La propiedad updateSelector debería estar definida.', - 'Are you sure you want to delete this item?' => 'Seguro que desea borrar este elemento?', + 'Are you sure you want to delete this item?' => '¿Está seguro que desea borrar este elemento?', 'Delete' => 'Borrar', - 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Desplegando {start}-{end} de 1 resultado.|Desplegando {start}-{end} de {count} resultados.', - 'Either "name" or "value" must be specified for CDataColumn.' => '"name" o "value" deben especificarse para CDataColumn.', + 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Viendo {start}-{end} de 1 resultado.|Viendo {start}-{end} de {count} resultados.', + 'Either "name" or "value" must be specified for CDataColumn.' => 'Deben especificarse ya sea "name" o "value" para CDataColumn.', + 'Home' => 'Inicio', 'No results found.' => 'No se encontraron resultados.', 'Not set' => 'No asignado', - 'Please specify the "attributes" property.' => 'Favor de especificar la propiedad "attributes".', - 'Please specify the "data" property.' => 'Favor de especificar la propiedad "data".', + 'Please specify the "attributes" property.' => 'Por favor especifique la propiedad "attributes".', + 'Please specify the "data" property.' => 'Por favor especifique la propiedad "data".', 'Sort by: ' => 'Ordenar por: ', 'The "dataProvider" property cannot be empty.' => 'La propiedad "dataProvider" no puede estar vacia.', 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'El atributo debe especificarse en el formato "Nombre:Tipo:Etiqueta", donde "Tipo" y "Etiqueta" son opcionales.', + 'The button type "{type}" is not supported.' => 'El tipo de botón "{type}" no es soportado.', 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'La columna debe especificarse en el formato "Nombre:Tipo:Etiqueta", donde "Tipo" y "Etiqueta" son opcionales.', 'The property "itemView" cannot be empty.' => 'La propiedad "itemView" no puede estar vacia.', + 'The property filterSelector should be defined.' => 'La propiedad filterSelector debería estar definida.', + 'The property updateSelector should be defined.' => 'La propiedad updateSelector debería estar definida.', 'Total 1 result.|Total {count} results.' => 'Total de 1 resultado.|Total de {count} resultados.', - 'Update' => 'Actualiza', - 'View' => 'Mostrar', - '{class} must specify "model" and "attribute" or "name" property values.' => '{class} debe especificar valor/es para "model" y "atributte" o para "name".', + 'Update' => 'Actualizar', + 'View' => 'Ver', + '{class} must specify "model" and "{attribute}" or "{name}" property values.' => '{class} debe especificar valores para las propiedades "model" y "{attribute}" o para "{name}".', ); diff --git a/framework/messages/fi/yii.php b/framework/messages/fi/yii.php index b317c12959..d20fd61af5 100644 --- a/framework/messages/fi/yii.php +++ b/framework/messages/fi/yii.php @@ -17,11 +17,10 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() ei pysty generoimaan satunnaista merkkijonoa nykyisessä ympäristössä.', - 'Unable to generate random string.' => 'Satunnaista merkkijonoa ei pysty generoimaan.', '"{path}" is not a valid directory.' => '"{path}" ei ole kelvollinen hakemisto.', '< Previous' => '< Edellinen', '<< First' => '<< Ensimmäinen', + 'A PHP extension stopped the file upload.' => 'PHP-laajennus on pysäyttänyt tiedoston latauksen.', 'Action class {class} must implement the "run" method.' => '{class}-toimintaluokan pitää toteuttaa "run"-metodi', 'Active Record requires a "db" CDbConnection application component.' => 'Active record vaatii "db" CDbConnection -sovelluskomponentin.', 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'Active record -luokalla "{class}" on virheellinen konfiguraatio relaatiolle "{relation}". Sen pitää määrittää relaation tyyppi, siihen liittyvä active record -luokka ja viiteavain (fk).', @@ -30,7 +29,6 @@ 'Adding a primary key after table has been created is not supported by SQLite.' => 'SQLite ei tue perusavaimen (pk) lisäystä taulun luonnin jälkeen.', 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'Alias "{alias}" on virheellinen. Varmista, että se osoittaa olemassa olevaan PHP-tiedostoon ja että tiedosto on luettavissa.', 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'Alias "{alias}" on virheellinen. Varmista, että se osoittaa olemassa olevaan hakemistoon tai tiedostoon.', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => 'Alias "{alias}" on virheellinen. Varmista, että se osoittaa olemassa olevaan hakemistoon.', 'Altering a DB column is not supported by SQLite.' => 'SQLite ei tue kentän muokkausta.', 'Application Log' => 'Sovellusloki', 'Application base path "{path}" is not a valid directory.' => 'Sovelluksen base-polku "{path}" ei ole kelvollinen hakemisto.', @@ -82,6 +80,7 @@ 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager vaatii, että PHP:n mcrypt-laajennus on ladattu, jotta tiedonsalaustoiminnallisuutta voi käyttää.', 'CSecurityManager.encryptionKey cannot be empty.' => 'CSecurityManager.encryptionKey ei voi olla tyhjä.', 'CSecurityManager.validationKey cannot be empty.' => 'CSecurityManager.validationKey ei voi olla tyhjä.', + 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() ei pysty generoimaan satunnaista merkkijonoa nykyisessä ympäristössä.', 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> hyväksyy vain {type}-luokan olioita.', 'CTypedMap<{type}> can only hold objects of {type} class.' => 'CTypedMap<{type}> hyväksyy vain {type}-luokan olioita.', 'CUrlManager.UrlFormat must be either "path" or "get".' => 'CUrlManager.UrlFormat pitää olla joko "path" tai "get".', @@ -93,7 +92,6 @@ 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'Kohdetta "{child}" ei voida lisätä kohteen "{parent}" lapseksi. Silmukka havaittu.', 'Cannot add "{name}" as a child of itself.' => 'Kohdetta "{name}" ei voida lisätä itsensä lapseksi.', 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'Kohdetta tyyppiä "{child}" ei voida lisätä kohteeseen tyyppiä "{parent}".', - 'Cannot hash a password that is empty or not a string.' => 'Tiivistettä ei voida luoda salasanalle, joka on tyhjä tai ei ole merkkijono.', 'Class name "{class}" does not match class file "{file}".' => 'Luokan nimi "{class}" ei vastaa luokkatiedostoa "{file}".', 'Column name must be either a string or an array.' => 'Kentän nimen pitää olla joko merkkijono tai taulukko.', 'Dropping DB column is not supported by SQLite.' => 'SQLite ei tue kentän poistoa.', @@ -108,7 +106,6 @@ 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Ei voitu asettaa "{class}"-luokan turvatonta attribuuttia "{attribute}".', 'Failed to start session.' => 'Ei voitu aloittaa sessiota.', 'Failed to write the uploaded file "{file}" to disk.' => 'Ladattua tiedostoa "{file}" ei voitu kirjoittaa levylle.', - 'File upload was stopped by extension.' => 'Laajennus on pysäyttänyt tiedoston latauksen.', 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'Suodatin "{filter}" on virheellinen. Käsittelijällä "{class}" ei ole suodattimen metodia "filter{filter}".', 'GD with FreeType or ImageMagick PHP extensions are required.' => 'GD FreeType-tuella tai ImageMagick PHP -laajennuksia tarvitaan.', 'Get a new code' => 'Pyydä uusi koodi', @@ -147,7 +144,7 @@ 'Table "{table}" does not have a column named "{column}".' => 'Talululla "{table}" ei ole "{column}"-nimistä kenttää.', 'The "db" application component must be configured to be a CDbConnection object.' => 'Sovelluksen "db"-komponentin tulee olla konfiguroituna CDbConnection-olioksi.', 'The "filter" property must be specified with a valid callback.' => '"filter"-ominaisuudelle pitää määrittää kelvollinen callback.', - 'The "forceCopy" and "linkAssets" cannot be both true.' => '"forceCopy" ja "linkAssets" eivät molemmat voi olla tosia.', + 'The "forceCopy" and "linkAssets" cannot be both true.' => '"forceCopy" ja "linkAssets" eivät molemmat voi olla tosi.', 'The "pattern" property must be specified with a valid regular expression.' => '"pattern"-ominaisuudelle pitää määrittää kelvollisen säännöllinen lausekke.', 'The "range" property must be specified with a list of values.' => '"range"-ominaisuudelle pitää määrittää arvoluettelo.', 'The $converter argument must be a valid callback or null.' => '$converter argumentin täytyy olla kelvollinen callback tai null.', @@ -191,7 +188,6 @@ 'The pattern for week in month must be "W".' => 'Kuukauden viikon muodon pitää olla "W".', 'The pattern for week in year must be "w".' => 'Vuoden viikon muodon pitää olla "w".', 'The queue is empty.' => 'Jono on tyhjä.', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Relaation "{relation}" active record -luokkaa "{class}" ei ole määritelty oikein. Viiteavaimelle (fk) annettua liitostaulua "{joinTable}" ei löydy tietokannasta.', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Relaation "{relation}" active record -luokkaa "{class}" ei ole määritelty oikein: Viiteavaimelle (fk) annettua liitostaulua "{joinTable}" ei löydy tietokannasta.', 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'Relaation "{relation}" active record -luokalle "{class}" on määritelty viiteavain (fk) "{key}", joka ei osoita ylätauluun "{table}".', 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'Relaation "{relation}" active record -luokalle "{class}" on määritelty puutteellinen viiteavain (fk). Viiteavaimen (fk) tulee koostua kentistä, jotka viittaavat molempiin liitostauluihin.', @@ -212,6 +208,7 @@ 'Unable to change the item name. The name "{name}" is already used by another item.' => 'Kohteen nimeä ei voi vaihtaa. Nimi "{name}" on jo toisen kohteen käytössä.', 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'Sovelluksen tilatiedostoa ei voitu luoda. Varmista, että web-palvelimen prosessilla on kirjoitusoikeus hakemistoon, jossa tiedosto sijaitsee, ja että hakemisto on olemassa.', 'Unable to find "{column}" in table "{table}".' => 'Taulusta "{table}" ei löydy kenttää "{column}".', + 'Unable to generate random string.' => 'Satunnaista merkkijonoa ei pysty generoimaan.', 'Unable to lock file "{file}" for reading.' => 'Tiedoston "{file}" lukitseminen lukua varten epäonnistui.', 'Unable to lock file "{file}" for writing.' => 'Tiedoston "{file}" lukitseminen kirjoitusta varten epäonnistui.', 'Unable to read file "{file}".' => 'Tiedostoa "{file}" ei voida lukea.', @@ -257,16 +254,16 @@ '{className} does not support flushValues() functionality.' => '{className} ei tue flushValues()-toiminnallisuutta.', '{className} does not support get() functionality.' => '{className} ei tue get()-toiminnallisuutta.', '{className} does not support set() functionality.' => '{className} ei tue set()-toiminnallisuutta.', - '{class} and its behaviors do not have a method or closure named "{name}".' => 'Luokan {class} ja sen käyttäytymismallit eivät sisällä metodia tai sulkeumaa nimeltä "{name}".', - '{class} does not have relation "{name}".' => 'Luokka {class} ei sisällä relaatiota "{name}".', - '{class} does not support fetching all table names.' => 'Luokka {class} ei tue kaikkien taulujen nimien hakua.', - '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => 'Luokka {class} sisältää virheellisen validointisäännön. Säännön täytyy määritellä validoitavat attribuutit sekä validaattorin nimi.', + '{class} and its behaviors do not have a method or closure named "{name}".' => '{class} ja sen käyttäytymismallit eivät sisällä metodia tai sulkeumaa nimeltä "{name}".', + '{class} does not have relation "{name}".' => '{class} ei sisällä relaatiota "{name}".', + '{class} does not support fetching all table names.' => '{class} ei tue kaikkien taulujen nimien hakua.', + '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} sisältää virheellisen validointisäännön. Säännön täytyy määritellä validoitavat attribuutit sekä validaattorin nimi.', '{class} must specify "model" and "attribute" or "name" property values.' => '{class}-luokan täytyy määrittää "model"- ja "attribute"- tai "name"-ominaisuuksien arvot.', - '{class} requires the Blowfish option of the PHP crypt() function. This system does not have it.' => '{class} vaatii Blowfish-option PHP:n crypt()-funktiolle. Tästä järjestelmästä se puuttuu.', - '{class} requires the PHP crypt() function. This system does not have it.' => '{class} vaatii PHP:n crypt()-funktion. Tästä järjestelmästä se puuttuu.', + '{class} requires the Blowfish option of the PHP crypt() function. This system does not have it.' => '{class} vaatii PHP crypt()-funktion Blowfish-option. Tästä järjestelmästä se puuttuu.', + '{class} requires the PHP crypt() function. This system does not have it.' => '{class} vaatii PHP crypt()-funktion. Tästä järjestelmästä se puuttuu.', '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin täytyy asettaa arvoon true, jotta evästepohjaista autentikointia voi käyttää.', '{class}::$cost must be a number.' => '{class}::$cost täytyy olla luku.', - '{class}::$cost must be between 4 and 31.' => '{class}::$cost täytyy olla väliltä 4 ja 31.', + '{class}::$cost must be between 4 and 31.' => '{class}::$cost täytyy olla 4 ja 31 väliltä.', '{class}::authenticate() must be implemented.' => '{class}::authenticate() täytyy olla toteutettuna.', '{controller} cannot find the requested view "{view}".' => '{controller} ei löydä pyydettyä näkymää "{view}".', '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} sisältää virheellisesti sisäkkäisiä widget-tageja "{view}"-näkymässä. {widget}-widget ei sisällä endWidget()-kutsua.', diff --git a/framework/messages/fi/zii.php b/framework/messages/fi/zii.php index fcc00e793b..7c6d4ab29f 100644 --- a/framework/messages/fi/zii.php +++ b/framework/messages/fi/zii.php @@ -37,6 +37,5 @@ 'Total 1 result.|Total {count} results.' => 'Yhteensä 1 tulos.|Yhteensä {count} tulosta.', 'Update' => 'Päivitä', 'View' => 'Näytä', - '{class} must specify "model" and "attribute" or "name" property values.' => 'Luokan {class} pitää määrittää "model"- ja "attribute"- tai "name"-ominaisuuksien arvot.', - '{class} must specify "model" and "{attribute}" or "{name}" property values.' => 'Luokan {class} pitää määrittää "model"- ja "{attribute}"- tai "{name}"-ominaisuuksien arvot.', + '{class} must specify "model" and "{attribute}" or "{name}" property values.' => '{class}-luokan täytyy määrittää "model"- ja "{attribute}"- tai "{name}"-ominaisuuksien arvot.', ); diff --git a/framework/messages/fr/yii.php b/framework/messages/fr/yii.php index d07fb9344d..4c131c15fb 100644 --- a/framework/messages/fr/yii.php +++ b/framework/messages/fr/yii.php @@ -21,18 +21,18 @@ 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'L\'alias « {alias} » est invalide. Vérifiez qu\'il pointe vers un fichier PHP existant.', 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => 'L\'alias « {alias} » est invalide. Vérifiez qu\'il pointe vers un dossier existant.', 'Altering a DB column is not supported by SQLite.' => 'Modifier une colonne n\'est pas supporté par SQLite.', - 'Application Log' => 'Log applicatif', - 'CDbCacheDependency.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCacheDependency.connectionID « {id} » est invalide. Vérifiez qu\'elle référence bien l\'ID d\'un composant d\'application de type CDbConnection.', - 'CDbConnection failed to open the DB connection.' => 'CDbConnection n\'a pas pu ouvrir de connexion vers la base de données.', - 'CDbTestFixture.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbTestFixture.connectionID « {id} » est invalide. Vérifiez qu\'elle référence bien l\'ID d\'un composant d\'application de type CDbConnection.', + 'Application Log' => 'Log de l\'Application', + 'CDbCacheDependency.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCacheDependency.connectionID « {id} » est invalide. Vérifiez qu\'il référence bien l\'ID d\'un composant d\'application de type CDbConnection.', + 'CDbConnection failed to open the DB connection.' => 'CDbConnection n\'a pas pu ouvrir de connexion à la base de données.', + 'CDbTestFixture.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbTestFixture.connectionID « {id} » est invalide. Vérifiez qu\'il référence bien l\'ID d\'un composant d\'application de type CDbConnection.', 'CTypedMap<{type}> can only hold objects of {type} class.' => 'TypedMap<{type}> peut seulement contenir des objets de la classe {type}.', - 'CWinCache requires PHP wincache extension to be loaded.' => 'CWinCache impose que l\'extension PHP wincache soit chargée.', - 'CWinCache user cache is disabled. Please set wincache.ucenabled to On in your php.ini.' => 'Le cache utilisateur CWinCache est désactivé. Veuillez définir la propriété du fichier php.ini wincache.ucenabled à On.', + 'CWinCache requires PHP wincache extension to be loaded.' => 'CWinCache nécessite que l\'extension PHP wincache soit chargée.', + 'CWinCache user cache is disabled. Please set wincache.ucenabled to On in your php.ini.' => 'Le cache utilisateur CWinCache est désactivé. Veuillez définir la propriété wincache.ucenabled à On dans le fichier php.ini.', 'Dropping DB column is not supported by SQLite.' => 'Supprimer une colonne n\'est pas supporté par SQLite.', 'Dropping a foreign key constraint is not supported by SQLite.' => 'Supprimer une contrainte de clef étrangère n\'est pas supporté par SQLite.', - 'Failed to initialize the mcrypt module.' => 'Impossible d\'initiliser le module mcrypt.', - 'Failed to set unsafe attribute "{attribute}".' => 'Impossilbe de définir l\'attribut « {attribute} ».', - 'GD and FreeType PHP extensions are required.' => 'Les extension PHP GD et FreeType sont nécessaires.', + 'Failed to initialize the mcrypt module.' => 'Impossible d\'initialiser le module mcrypt.', + 'Failed to set unsafe attribute "{attribute}".' => 'Impossible de définir l\'attribut « {attribute} ».', + 'GD and FreeType PHP extensions are required.' => 'Les extensions PHP GD et FreeType sont nécessaires.', 'Property CMaskedTextField.mask cannot be empty.' => 'La propriété CMaskedTextField.mask ne peut être vide.', 'Renaming a DB column is not supported by SQLite.' => 'Renommer une colonne n\'est pas supporté par SQLite', 'The "db" application component must be configured to be a CDbConnection object.' => 'Le composant d\'application « db » doit être configuré pour que ce soit un objet de type CDbConnection.', @@ -41,8 +41,8 @@ 'The pattern for day of the week must be "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" or "ccccc".' => 'Le motif de définition du jour de la semaine doit être « E », « EE », « EEE », « EEEE », « EEEEE », « e », « ee », « eee », « eeee », « eeeee », « c », « cccc » ou « ccccc ».', 'The pattern for month must be "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" or "LLLL".' => 'Le motif de définition du mois doit être « M », « MM », « MMM », « MMMM », « L », « LL », « LLL » ou « LLLL ».', 'The requested view "{name}" was nut found.' => 'La vue demandée « {name} » n\'a pas été trouvée.', - 'Unable to find "{column}" in table "{table}".' => 'Impossible de trouver « {column} » au sein de la table « {table} ».', - 'Unable to import "{alias}". Please check your server configuration to make sure you are allowed to change PHP include_path.' => 'Impossible d\'importer « {alias} ». Vérifiez la configuration de votre serveur pour être certain de pouvoir modifier la valeur PHP include_path.', + 'Unable to find "{column}" in table "{table}".' => 'Impossible de trouver « {column} » dans la table « {table} ».', + 'Unable to import "{alias}". Please check your server configuration to make sure you are allowed to change PHP include_path.' => 'Impossible d\'importer « {alias} ». Vérifiez la configuration de votre serveur pour être certain que vous pouvez modifier la valeur de PHP include_path.', 'Unable to remove migration {class}.' => 'Impossible de supprimer la migration {class}.', 'Unknown operator "{operator}".' => 'L\'opérateur « {operator} » est inconnu.', 'Unknown type "{type}".' => 'Le type « {type} » est inconnu.', @@ -55,12 +55,12 @@ '<< First' => '<< Début', 'Active Record requires a "db" CDbConnection application component.' => 'Un composant d\'application « db » de type CDbConnection est nécessaire pour les Active Record.', 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'La relation « {relation} » de la classe Active Record « {class} » est invalide. La classe et la clef étrangère de la classe Active record associée doivent être spécifiées.', - 'Active record "{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.' => 'La colonne « {column} » que tente d\'accéder l\'Active Record « {class} » est invalide. Note, la colonne doit exister au sein de la table ou être un l\'alias d\'une expression.', + 'Active record "{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.' => 'La colonne « {column} » que tente d\'accéder l\'Active Record « {class} » est invalide. Note, la colonne doit exister au sein de la table ou être une expression avec un alias.', 'Active record class "{class}" does not have a scope named "{scope}".' => 'La classe Active record « {class} n\'a pas de scope nommé « {scope} ».', 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'L\'alias « {alias} » est invalide. Vérifiez qu\'il pointe sur un fichier ou un dossier valide.', 'Application base path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » du dossier de base de l\'application est invalide.', 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.' => 'Le chemin d\'accès « {path} » du dossier d\'exécution est invalide. Vérifiez que le proccessus du serveur web peut y accéder en écriture.', - 'Authorization item "{item}" has already been assigned to user "{user}".' => 'L\'élément d\'autorisation « {item} » a déjà été assigné à l\'utilisateur « {user} ».', + 'Authorization item "{item}" has already been assigned to user "{user}".' => 'L\'élément d\'Autorisation « {item} » a déjà été assigné à l\'utilisateur « {user} ».', 'Base path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » de base n\'est pas un dossier valide.', 'CApcCache requires PHP apc extension to be loaded.' => 'L\'extension PHP apc doit être chargée pour pouvoir utiliser CApcCache.', 'CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.' => 'La propriété CAssetManager.basePath « {path} » est invalide. Vérifiez que le dossier existe et que le proccessus du serveur web peut y accéder en écriture.', @@ -71,17 +71,17 @@ 'CDbCacheDependency.sql cannot be empty.' => 'La propriété CDbCacheDependency.sql ne peut être vide.', 'CDbCommand failed to execute the SQL statement: {error}' => 'CDbCommand n\'a pas pu exécuter la commande SQL : {error}', 'CDbCommand failed to prepare the SQL statement: {error}' => 'CDbCommand n\'a pas pu préparer la commande SQL : {error}', - 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection ne supporte pas la lecture de schéma pour les bases de données {driver}.', + 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection ne supporte pas la lecture du schéma de la base de données {driver}.', 'CDbConnection failed to open the DB connection: {error}' => 'CDbConnection n\'a pu ouvrir la connexion à la base de données : {error}', 'CDbConnection is inactive and cannot perform any DB operations.' => 'CDbConnection est inactive et ne peut effectuer aucune opération sur la base de données.', 'CDbConnection.connectionString cannot be empty.' => 'La propriété CDbConnection.connectionString ne peut être vide.', - 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader ne peut reculer. Il peut seulement avancer.', + 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader ne peut reculer. Seule la lecture en avant est possible.', 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'La propriété CDbHttpSession.connectionID « {id} » est invalide. Vérifiez qu\'il référence l\'ID d\'un composant d\'application de type CDbConnection.', 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'La propriété CDbLogRoute.connectionID « {id} » ne pointe pas vers un composant d\'application de type CDbConnection valide.', - 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'La propriété CDbMessageSource.connectionI est invalide. Vérifiez que « {id} » référence un composant d\'application de base de données valide.', + 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'La propriété CDbMessageSource.connectionID est invalide. Vérifiez que « {id} » référence un composant d\'application de base de données valide.', 'CDbTransaction is inactive and cannot perform commit or roll back operations.' => 'CDbTransaction est inactif et ne peut donc valider ou annuler des opérations.', 'CDirectoryCacheDependency.directory cannot be empty.' => 'La propriété CDirectoryCacheDependency.directory ne peut être vide.', - 'CEAcceleratorCache requires PHP eAccelerator extension to be loaded, enabled or compiled with the "--with-eaccelerator-shared-memory" option.' => 'CEAcceleratorCache a besoin de l\'extension PHP eAccelerator pour se charger, activée ou compilée avec l\'option « --with-eaccelerator-shared-memory ».', + 'CEAcceleratorCache requires PHP eAccelerator extension to be loaded, enabled or compiled with the "--with-eaccelerator-shared-memory" option.' => 'CEAcceleratorCache a besoin de l\'extension PHP eAccelerator pour être chargée, activée ou compilée avec l\'option « --with-eaccelerator-shared-memory ».', 'CFileCacheDependency.fileName cannot be empty.' => 'La propriété CFileCacheDependency.fileName ne peut être vide.', 'CFileLogRoute.logPath "{path}" does not point to a valid directory. Make sure the directory exists and is writable by the Web server process.' => 'CFileLogRoute.logPath « {path} » ne pointe pas vers un dossier valide. Vérifiez que le proccessus du serveur web peut y accéder en écriture.', 'CFilterChain can only take objects implementing the IFilter interface.' => 'CFilterChain peut seulement accepter des objets qui implémentent l\'interface IFilter', @@ -89,37 +89,37 @@ 'CFlexWidget.name cannot be empty.' => 'La propriété CFlexWidget.name ne peut être vide.', 'CGlobalStateCacheDependency.stateName cannot be empty.' => 'La propriété CGlobalStateCacheDependency.stateName ne peut être vide.', 'CHttpCookieCollection can only hold CHttpCookie objects.' => 'CHttpCookieCollection peut seulement contenir des objets de type CHttpCookie.', - 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest ne peut déterminer le script de base de l\'URL', - 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest ne peut déterminer le chemin de la requête', + 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest ne peut déterminer l\'URL du script de base.', + 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest ne peut déterminer le chemin de la requête.', 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest ne peut déterminer l\'URI de la requête.', - 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'La propriété CHttpSession.cookieMode peut prendre la valeur « none », « allow » or « only ».', + 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'La propriété CHttpSession.cookieMode peut uniquement prendre la valeur « none », « allow » ou « only ».', 'CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.' => 'La propriété CHttpSession.gcProbability « {value} » est invalide. Ce doit être un entier compris entre 0 et 100.', 'CHttpSession.savePath "{path}" is not a valid directory.' => 'La valeur de la propriété CHttpSession.savePath « {path} » n\'est pas un dossier valide.', 'CMemCache server configuration must be an array.' => 'La configuration serveur de CMemCache doit être un array.', - 'CMemCache server configuration must have "host" value.' => 'La configuration serveur de CMemCache doit avoir une valeur « host ».', - 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute a trouvé un bloc de code « {token} » incorrect. Vérifiez que les appels Yii::beginProfile() et Yii::endProfile() sont correctement imbriqués.', + 'CMemCache server configuration must have "host" value.' => 'La configuration serveur de CMemCache doit avoir une valeur pour « host ».', + 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute a trouvé un bloc de code « {token} » incorrect. Vérifiez que les appels à Yii::beginProfile() et à Yii::endProfile() sont correctement imbriqués.', 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".' => 'La propriété CProfileLogRoute.report « {report} » est invalide. Les valeurs autorisées sont « summary » et « callstack ».', 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'L\'extension PHP mcrypt doit être chargée pour pouvoir utiliser CSecurityManager et activer les fonctionnalités de cryptage.', 'CSecurityManager.encryptionKey cannot be empty.' => 'La propriété CSecurityManager.encryptionKey ne peut être vide.', 'CSecurityManager.validationKey cannot be empty.' => 'La propriété CSecurityManager.validationKey ne peut être vide.', 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> peut uniquement contenir des objets de type {type}.', 'CUrlManager.UrlFormat must be either "path" or "get".' => 'La propriété CUrlManager.UrlFormat doit être « path » ou « get ».', - 'CXCache requires PHP XCache extension to be loaded.' => 'L\'extension PHP XCache doit être chargée pour pouvoir utiliser CXCache.', - 'CZendDataCache requires PHP Zend Data Cache extension to be loaded.' => 'CZendDataCache nécessite l\'extension PHP Zend Data Cache pour être chargé.', + 'CXCache requires PHP XCache extension to be loaded.' => 'L\'extension PHP XCache doit être chargée CXCache.', + 'CZendDataCache requires PHP Zend Data Cache extension to be loaded.' => 'L\'extension PHP Zend Data Cache doit être chargée pour pouvoir utiliser CZendDataCache.', 'Cannot add "{child}" as a child of "{name}". A loop has been detected.' => 'Impossible d\'ajouter « {child} » en tant qu\'enfant de « {name} ». Une boucle a été détectée.', 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'Impossible d\'ajouter « {child} » en tant qu\'enfant de « {parent} ». Une boucle a été détectée.', 'Cannot add "{name}" as a child of itself.' => 'Impossible d\'ajouter « {name} » en tant qu\'enfant de lui-même.', 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'Impossible d\'ajouter un élément de type « {child} » à un élément de type « {parent} ».', 'Column name must be either a string or an array.' => 'Le nom de la colonne doit être une chaine de caractères ou un tableau.', - 'Either "{parent}" or "{child}" does not exist.' => '« {parent} » ou « {child} » est inexistant.', + 'Either "{parent}" or "{child}" does not exist.' => 'Soit « {parent} » soit « {child} » n\'existe pas.', 'Error: Table "{table}" does not have a primary key.' => 'Erreur : la table « {table} » n\'a pas de clef primaire.', 'Error: Table "{table}" has a composite primary key which is not supported by crud command.' => 'Erreur : la table « {table} » a une clef primaire composite qui n\'est pas supportée par les commandes CRUD.', 'Event "{class}.{event}" is attached with an invalid handler "{handler}".' => 'L\'événement « {class}.{event} » est associé à un gestionnaire d\'événement « {handler} » invalide.', 'Event "{class}.{event}" is not defined.' => 'L\'événement « {class}.{event} » n\'est pas défini.', 'Extension path "{path}" does not exist.' => 'Le chemin d\'accès « {path} » de l\'extension n\'existe pas.', 'Failed to write the uploaded file "{file}" to disk.' => 'Impossible d\'écrire le fichier téléchargé « {file} » sur le disque', - 'File upload was stopped by extension.' => 'Le téléchargement a été stoppé apr extension.', - 'Filter "{filter}" is invalid. Controller "{class}" does have the filter method "filter{filter}".' => 'Le filtre « {filter} » est invalide. Le contrôleur « {class} » n\'implémente pas la méthode de filtrage « filter{filter} ».', + 'File upload was stopped by extension.' => 'Le téléchargement a été stoppé par l\'extension.', + 'Filter "{filter}" is invalid. Controller "{class}" does have the filter method "filter{filter}".' => 'Le filtre « {filter} » est invalide. Le contrôleur « {class} » possède la méthode de filtrage « filter{filter} ».', 'Get a new code' => 'Récupérez un nouveau code', 'Go to page: ' => 'Aller à la page :', 'Invalid MO file revision: {revision}.' => 'Version du fichier MO invalide: {revision}.', @@ -128,13 +128,13 @@ 'Invalid operator "{operator}".' => 'Opérateur invalide « {operator} ».', 'Last >>' => 'Fin >>', 'List data must be an array or an object implementing Traversable.' => 'La liste de données doit être un array ou un objet qui implémente Traversable.', - 'List index "{index}" is out of bound.' => 'L\'index « {index} » est en dehors de la liste.', + 'List index "{index}" is out of bound.' => 'L\'index « {index} » est en dehors des limites.', 'Login Required' => 'Identifiant requis', 'Map data must be an array or an object implementing Traversable.' => 'Les données doivent être un array ou un objet qui implémente Traversable.', - 'Missing the temporary folder to store the uploaded file "{file}".' => 'Le dossier temporaire permettant de stocker le fichier téléchargédes « {file} » est inexistant.', + 'Missing the temporary folder to store the uploaded file "{file}".' => 'Le dossier temporaire permettant de stocker le fichier téléchargé « {file} » est inexistant.', 'Next >' => 'Suivant >', 'No columns are being updated for table "{table}".' => 'Aucune colonne de la table « {table} » ne sera mise à jour.', - 'No counter columns are being updated for table "{table}".' => 'Ancune colonne incrémentale de la table « {table} » ne sera mise à jour.', + 'No counter columns are being updated for table "{table}".' => 'Aucune colonne incrémentale de la table « {table} » ne sera mise à jour.', 'Object configuration must be an array containing a "class" element.' => 'L\'objet configuration doit être un tableau contenant un élément « class ».', 'Please fix the following input errors:' => 'Veuillez corriger les erreurs de saisie :', 'Property "{class}.{property}" is not defined.' => 'La propriété « {class}.{property} » est indéfinie.', @@ -144,15 +144,15 @@ 'Stack data must be an array or an object implementing Traversable.' => 'Les données de la pile doivent être un tableau ou un objet qui implémente Traversable.', 'Table "{table}" does not exist.' => 'La table « {table} » n\'existe pas.', 'Table "{table}" does not have a column named "{column}".' => 'La table « {table} » n\'a pas de colonne « {column} ».', - 'The "filter" property must be specified with a valid callback.' => 'La propriété « filter » doit contenir une fonction de rappel valide.', + 'The "filter" property must be specified with a valid callback.' => 'La propriété « filter » doit contenir une fonction de rappel (callback) valide.', 'The "pattern" property must be specified with a valid regular expression.' => 'La propriété « pattern » doit contenir une expression rationnelle valide.', - 'The CSRF token could not be verified.' => 'Le jeton CSRF n\'a pu être vérifié.', + 'The CSRF token could not be verified.' => 'Le jeton CSRF n\'a pas pu être vérifié.', 'The STAT relation "{name}" cannot have child relations.' => 'La relation STAT « {name} » ne peut avoir de relations enfants.', 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'Le motif « {pattern} » pour la route « {route} » n\'est pas un expression rationnelle valide.', 'The active record cannot be deleted because it is new.' => 'L\'active record ne peut être supprimé car il vient d\'être défini.', 'The active record cannot be inserted to database because it is not new.' => 'L\'active record ne peut être inséré en base de données car il ne vient pas d\'être défini.', 'The active record cannot be updated because it is new.' => 'L\'active record ne peut être mis à jour car il vient d\'être défini.', - 'The asset "{asset}" to be published does not exist.' => 'L\'élément « {asset} » à publier n\'existe pas.', + 'The asset "{asset}" to be published does not exist.' => 'L\'asset « {asset} » à publier n\'existe pas.', 'The command path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » à la ligne de commande n\'est pas un dossier valide.', 'The controller path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » au contrôleur n\'est pas un dossier valide.', 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.' => 'Le fichier « {file} » ne peut être téléchargé. Les extensions de fichier autorisées sont : {extensions}.', @@ -164,7 +164,7 @@ 'The item "{parent}" already has a child "{child}".' => 'L\'élément « {parent} » a déjà un enfant « {child} ».', 'The layout path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » au gabarit n\'est pas un dossier valide.', 'The list is read only.' => 'La liste est en lecture seule.', - 'The map is read only.' => 'Le map est en lecture seule.', + 'The map is read only.' => 'La map est en lecture seule.', 'The module path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » au module n\'est pas un dossier valide.', 'The pattern for 12 hour format must be "h" or "hh".' => 'Le motif de définition des heures au format 12h doit être « h » ou « hh ».', 'The pattern for 24 hour format must be "H" or "HH".' => 'Le motif de définition des heures au format 24h doit être « H » ou « HH ».', @@ -181,12 +181,12 @@ 'The pattern for week in month must be "W".' => 'Le motif de définition du numéro de semaine dans le mois doit être « W ».', 'The pattern for week in year must be "w".' => 'Le motif de définition du numéro de semaine dans l\'année doit être « w ».', 'The queue is empty.' => 'La queue est vide.', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relation « {relation} » dans la classe Active record « {class} » n\'est pas définie correctement. La table de jointure « {joinTable} » donnée de la clef étrangère n\'est pas trouvée dans la base de données.', + 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relation « {relation} » dans la classe Active record « {class} » n\'est pas définie correctement. La table de jointure « {joinTable} » donnée par la clef étrangère n\'a pas été trouvée dans la base de données.', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'La relation « {relation} » définie dans la classe Active record « {class} » est incorrecte : la table de jointure « {joinTable} » spécifiée par la clef étrangère est introuvable dans la base de données.', 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'La relation « {relation} » dans la classe Active record « {class} » est définie avec la clef étrangère « {key} » qui ne pointe pas vers la table parente « {table} ».', - 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'La relation « {relation} » définie dans l\'active record « {class} » a une clef étrangère incomplète. La clef étrangère doit être constituée des colonnes qui référence les deux tables.', + 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'La relation « {relation} » définie dans l\'active record « {class} » a une clef étrangère incomplète. La clef étrangère doit être constituée de colonnes qui font référence aux deux tables jointes.', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".' => 'La relation « {relation} » définie dans la classe active record « {class} » a une clef étrangère « {key} » invalide. Cette colonne n\'existe pas dans la table « {table} ».', - 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "{table}".' => 'La relation « {relation} » définie dans la classe active record « {class} » a une clef étrangère invalide. Les colonnes de la clef doivent correspondre avec les clefs primaire de la table « {table} ».', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "{table}".' => 'La relation « {relation} » définie dans la classe active record « {class} » a une clef étrangère invalide. Les colonnes de la clef doivent correspondre aux clefs primaires de la table « {table} ».', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".' => 'La relation « {relation} » définie dans la classe active record « {class} » a une clef étrangère invalide. Le clef étrangère doit être sous le format « joinTable(fk1,fk2,...) ».', 'The stack is empty.' => 'La pile est vide.', 'The system is unable to find the requested action "{action}".' => 'Le système ne peut trouver l\'action « {action} » demandée.', @@ -197,7 +197,7 @@ 'The view path "{path}" is not a valid directory.' => 'Le chemin d\'accès « {path} » à la vue n\'est pas un dossier valide.', 'Theme directory "{directory}" does not exist.' => 'Le dossier « {directory} » des thèmes est inexistant.', 'This content requires the Adobe Flash Player.' => 'Ce contenu nécessite le Lecteur Flash d\'Adobe.', - 'Unable to add an item whose name is the same as an existing item.' => 'Impossible d\'ajouter un élément dont le nom est identique à celui d\'un autre éléménet.', + 'Unable to add an item whose name is the same as an existing item.' => 'Impossible d\'ajouter un élément dont le nom est identique à celui d\'un autre élément.', 'Unable to change the item name. The name "{name}" is already used by another item.' => 'Impossible de changer le nom de l\'élément. Le nom « {name} » est déjà affecté à un autre élémnent.', 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'Impossible de créer le fichier « {file} » de gestion d\'état de l\'application. Vérifiez que le fichier existe et que le proccessus du serveur web peut y accéder en écriture.', 'Unable to lock file "{file}" for reading.' => 'Impossible de verrouiller le fichier « {file} » en lecture.', @@ -206,8 +206,8 @@ 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'Impossible de rejouer l\'action « {object}.{method} ». La méthode n\'existe pas.', 'Unable to resolve the request "{route}".' => 'Impossible de résoudre la requête « {route} ».', 'Unable to write file "{file}".' => 'Impossible d\'écrire dans le fichier « {file} ».', - 'Unknown authorization item "{name}".' => 'L\'élément « {name} » d\'autorisation est inconnu.', - 'Unrecognized locale "{locale}".' => 'La locale « {locale} » n\'a pas été reconnue.', + 'Unknown authorization item "{name}".' => 'L\'élément d\'autorisation « {name} » est inconnu.', + 'Unrecognized locale "{locale}".' => 'La localisation « {locale} » n\'a pas été reconnue.', 'View file "{file}" does not exist.' => 'Le fichier de la vue « {file} » est inexistant.', 'Yii application can only be created once.' => 'Une seule application Yii peut être créée.', 'You are not authorized to perform this action.' => 'Vous n\'êtes pas autorisé à effectuer cette action.', @@ -242,8 +242,8 @@ '{class} does not have a method named "{name}".' => '{class} n\'a pas de méthode « {name} ».', '{class} does not have relation "{name}".' => '{class} n\'a pas de relation « {name} ».', '{class} does not support fetching all table names.' => '{class} ne peut récupérer tous les noms des tables.', - '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} a une règle de validation invalide. La règle doit spécifier les attributs à valider et le nom du valideur.', - '{class} must specify "model" and "attribute" or "name" property values.' => '{class} doit définir le valeurs des propriétés « model » et « attribute » ou « name ».', + '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} a une règle de validation invalide. La règle doit spécifier les attributs à valider et le nom du validateur.', + '{class} must specify "model" and "attribute" or "name" property values.' => '{class} doit définir les valeurs des propriétés « model » et « attribute » ou « name ».', '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => 'La propriété {class}.allowAutoLogin doit être à true pour pouvoir utiliser l\'autentification par cookie.', '{class}::authenticate() must be implemented.' => '{class}::authenticate() doit être implémentée.', '{controller} cannot find the requested view "{view}".' => '{controller} ne peut trouver la vue « {view} ».', diff --git a/framework/messages/hu/zii.php b/framework/messages/hu/zii.php index 20bef87f97..06b42193a8 100644 --- a/framework/messages/hu/zii.php +++ b/framework/messages/hu/zii.php @@ -29,8 +29,8 @@ 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Az értékeket az alábbi formában kell megadni "Name:Type:Label" a "Type" és a "Label" nem kötelező.', 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Az oszlopot az alábbi formában kell megadni "Name:Type:Label". A "Type" és a "Label" nem kötelező.', 'The property "itemView" cannot be empty.' => 'Az "itemView" változó nem lehet üres.', - 'Total 1 result.|Total {count} results.' => 'Összesen 1 találat.|Összesen {count} találat.', + 'Total 1 result.|Total {count} results.' => 'Összesen {count} találat.', 'Update' => 'Szerkeszt', 'View' => 'Mutat', '{class} must specify "model" and "attribute" or "name" property values.' => '{class} meg kell határozni a "model" és a "attribute" vagy "name" értékét.', -); \ No newline at end of file +); diff --git a/framework/messages/ja/yii.php b/framework/messages/ja/yii.php index cc78bc67e8..d104a0dcf3 100644 --- a/framework/messages/ja/yii.php +++ b/framework/messages/ja/yii.php @@ -20,6 +20,7 @@ '"{path}" is not a valid directory.' => '{path} は有効なディレクトリではありません。', '< Previous' => '< 前', '<< First' => '<< 最初', + 'A PHP extension stopped the file upload.' => 'PHP の拡張モジュールがファイルのアップロードを中止しました。', 'Action class {class} must implement the "run" method.' => 'アクションクラス{class}は"run"メソッドを実装する必要があります。', 'Active Record requires a "db" CDbConnection application component.' => 'Active Record は "db" という CDbConnection アプリケーションコンポーネントを必要とします。', 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'アクティブレコード "{class}" の、リレーション {relation} のコンフィギュレーションに誤りがあります。リレーションタイプ、関係するアクティブレコードクラスと外部キーを指定して下さい。', @@ -28,7 +29,6 @@ 'Adding a primary key after table has been created is not supported by SQLite.' => 'SQLiteでは既存のテーブルにプライマリキーを追加することはできません。', 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => '"{alias}"は無効です。phpファイルが存在し、読み込み可能か確認してください。', 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'パスエイリアス "{alias}" が間違っています。存在するディレクトリやファイルを指しているかを確認してください。', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => '"{alias}"は無効です。ディレクトリが存在するか確認してください。', 'Altering a DB column is not supported by SQLite.' => 'SQLiteではデータベースのカラムを変更することはできません。', 'Application Log' => 'アプリケーションログ', 'Application base path "{path}" is not a valid directory.' => 'アプリケーションベースパス "{path}" は有効なディレクトリではありません。', @@ -92,7 +92,6 @@ 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => '"{parent}" の子供として "{child}" は追加できません。ループが検出されました。', 'Cannot add "{name}" as a child of itself.' => '"{name}" の子供として自分自身は追加できません。', 'Cannot add an item of type "{child}" to an item of type "{parent}".' => '"{parent}" 型の項目に "{child}" 型の項目は追加できません。', - 'Cannot hash a password that is empty or not a string.' => '空白のパスワードや文字列でないパスワードはハッシュ出来ません。', 'Class name "{class}" does not match class file "{file}".' => 'クラス名 "{class}" がクラスファイル "{file}"と一致しません。', 'Column name must be either a string or an array.' => 'カラム名は文字列または配列でなければいけません。', 'Dropping DB column is not supported by SQLite.' => 'SQLiteではデータベースカラムを削除することはできません。', @@ -107,7 +106,6 @@ 'Failed to set unsafe attribute "{attribute}" of "{class}".' => '"{class}" の安全でない属性"{attribute}"をセットできませんでした。', 'Failed to start session.' => 'セッションを開始できませんでした。', 'Failed to write the uploaded file "{file}" to disk.' => 'アップロードされたファイル "{file}" をディスクに書き込めませんでした。', - 'File upload was stopped by extension.' => 'ファイルアップロードが拡張により停止されました。', 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => '"{filter}"というフィルタは無効です。コントローラ"{class}"にはフィルタメソッド"filter{filter}"は存在しません。', 'GD with FreeType or ImageMagick PHP extensions are required.' => 'GD と FreeType、または ImageMagick の PHP 拡張が必要です。', 'Get a new code' => '新しいコードを取得', @@ -151,7 +149,6 @@ 'The "range" property must be specified with a list of values.' => '"range"プロパティは値のリストとして指定される必要があります。', 'The $converter argument must be a valid callback or null.' => '引数 $converter は有効なコールバックまたは null でなければなりません。', 'The CSRF token could not be verified.' => 'CSRFトークンを検証できませんでした。', - 'The DB query must contain the "from" portion.' => 'データベースクエリは"from"部を持つ必要があります。', 'The STAT relation "{name}" cannot have child relations.' => 'STATリレーション"{name}"は子リレーションを持つことができません。', 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'ルート "{route}" に対するURLパターン "{pattern}" は有効な正規表現ではありません。', 'The active record cannot be deleted because it is new.' => '新規のためアクティブレコードは削除できません。', @@ -190,7 +187,6 @@ 'The pattern for week in month must be "W".' => 'ひと月の週のパターンは "W" です。', 'The pattern for week in year must be "w".' => '一年の週のパターンは "w" です。', 'The queue is empty.' => 'キューが空です。', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'アクティブレコードクラス"{class}"のリレーション"{relation}"は正しく指定されていません。外部キーで与えられるジョインテーブル"{joinTable}"はデータベースに存在しません。', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'アクティブレコードクラス "{class}" のリレーション "{relation}" は正しく指定されていません。外部キーで与えられるジョインテーブル "{joinTable}" がデータベース中に見つかりません。', 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'アクティブレコードクラス"{class}"のリレーション"{relation}"には外部キー"{key}"が指定されていますが、親テーブル"{table}"を示していません。', 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'アクティブレコードクラス "{class}" のリレーション "{relation}" には不完全な外部キーが指定されています。外部キーは両方のジョインテーブルを参照するカラムで構成しなければいけません。', @@ -217,6 +213,7 @@ 'Unable to read file "{file}".' => 'ファイル "{file}" が読み込めません。', 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'アクション "{object}.{method}" をリプレーできません。メソッドが存在しません。', 'Unable to resolve the request "{route}".' => 'リクエスト"{route}"を解決できません。', + 'Unable to upload the file "{file}" because of an unrecognized error.' => '認識できないエラーにより、ファイル "{file}" をアップロード出来ません。', 'Unable to write file "{file}".' => 'ファイル "{file}" に書き込めません。', 'Unknown authorization item "{name}".' => '認証項目 "{name}" は不明です。', 'Unknown operator "{operator}".' => '"{operator}"は不明なオペレータです。', diff --git a/framework/messages/lv/yii.php b/framework/messages/lv/yii.php index e29c045202..befb3722c2 100644 --- a/framework/messages/lv/yii.php +++ b/framework/messages/lv/yii.php @@ -191,7 +191,7 @@ 'You are not authorized to perform this action.' => 'Jūs neesat autorizēts veikt šo darbību.', 'Your request is not valid.' => 'Jūsu pieprasījums ir nekorekts.', '{attribute} "{value}" has already been taken.' => 'Atribūts {attribute} "{value}" jau ir aizņemts.', - '{attribute} "{value}" is invalid.' => 'Atribūts {attribute} "{value}" ir nekorkts.', + '{attribute} "{value}" is invalid.' => 'Atribūts {attribute} "{value}" ir nekorekts.', '{attribute} cannot be blank.' => 'Lauks \'{attribute}\' ir obligāts.', '{attribute} is invalid.' => 'Lauks \'{attribute}\' ir nekorekts.', '{attribute} is not a valid URL.' => 'Lauks \'{attribute}\' nav korekts URL.', diff --git a/framework/messages/nl/yii.php b/framework/messages/nl/yii.php index 2188aa021a..295ffbc7b8 100644 --- a/framework/messages/nl/yii.php +++ b/framework/messages/nl/yii.php @@ -17,11 +17,11 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.' => 'Bij het gebruik van PHP 5.1.1 of lager zijn alleen de MD5 en SHA1 hashing algoritmes ondersteund.', - 'Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".' => 'Voor "CClientScript::POS_LOAD" en "CClientScript::POS_READY" zijn geen script HTML opties toegelaten.', + 'Unable to upload the file "{file}" because of an unrecognized error.' => 'Kon het bestand "{file}" niet opladen vanwege een onbekende fout.', '"{path}" is not a valid directory.' => '"{path}" is geen geldige map.', '< Previous' => '< Vorige', '<< First' => '<< Eerste', + 'A PHP extension stopped the file upload.' => 'Het opladen van het bestand werd gestopt door een PHP extensie.', 'Action class {class} must implement the "run" method.' => 'De Action class {class} moet de "run" functie implementeren.', 'Active Record requires a "db" CDbConnection application component.' => 'Active Record vereist een "db" CDbConnection applicatie-component.', 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.' => 'Active record "{class}" bevat een relatie "{relation}" met ongeldige instellingen. Het relatietype, de verwante active record class en de foreign key moeten opgegeven zijn.', @@ -30,7 +30,6 @@ 'Adding a primary key after table has been created is not supported by SQLite.' => 'SQLite kan geen primary key toevoegen nadat de tabel aangemaakt is.', 'Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.' => 'Alias "{alias}" is ongeldig. Zorg ervoor dat er naar een bestaand PHP bestand verwezen wordt.', 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'Alias "{alias}" is ongeldig. Zorg ervoor dat er naar een bestaande map of bestand verwezen wordt.', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory.' => 'Alias "{alias}" is ongeldig. Zorg ervoor dat er naar een bestaande map verwezen wordt.', 'Altering a DB column is not supported by SQLite.' => 'Een DB kolom aanpassen wordt niet ondersteund door SQLite.', 'Application Log' => 'Applicatielogboek', 'Application base path "{path}" is not a valid directory.' => 'Het base path voor de applicatie "{path}" is ongeldig.', @@ -82,6 +81,7 @@ 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager vereist de PHP mcrypt extensie om de data te kunnen versleutelen.', 'CSecurityManager.encryptionKey cannot be empty.' => 'CSecurityManager.encryptionKey mag niet leeg zijn.', 'CSecurityManager.validationKey cannot be empty.' => 'CSecurityManager.validationKey mag niet leeg zijn.', + 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() kan geen willekeurige string aanmaken binnen de huidige omgeving.', 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> kan alleen {type}-objecten bevatten.', 'CTypedMap<{type}> can only hold objects of {type} class.' => 'CTypedMap<{type}> kan alleen {type}-objecten bevatten.', 'CUrlManager.UrlFormat must be either "path" or "get".' => 'CUrlManager.UrlFormat moet "path" of "get". zijn', @@ -93,7 +93,6 @@ 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'Kan "{child}" niet toevoegen als kind van "{parent}". Er werd een lus ontdekt.', 'Cannot add "{name}" as a child of itself.' => 'Kan "{child}" niet toevoegen aan zichzelf.', 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'Kan een item van type "{child}" niet toevoegen aan een item van type "{parent}".', - 'Cannot hash a password that is empty or not a string.' => 'Van een paswoord dat leeg is of geen string kan geen has gegenereerd worden.', 'Class name "{class}" does not match class file "{file}".' => 'De class-naam "{class}" komt niet overeen met het bestand "{file}".', 'Column name must be either a string or an array.' => 'De kolomnaam moet een string of een array zijn.', 'Dropping DB column is not supported by SQLite.' => 'Een DB kolom verwijderen wordt niet ondersteund door SQLite.', @@ -108,7 +107,6 @@ 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Het toekennen van onveilig attribuut "{attribute}" op "{class}" is mislukt.', 'Failed to start session.' => 'Het starten van een sessie is mislukt', 'Failed to write the uploaded file "{file}" to disk.' => 'Kon het geuploade bestand "{file}" niet wegschrijven naar de schijf.', - 'File upload was stopped by extension.' => 'De bestandsupload werd gestopt door de extensie.', 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'Filter "{filter}" is ongeldig. Controller "{class}" heeft geen "filter{filter}" functie.', 'GD with FreeType or ImageMagick PHP extensions are required.' => 'GD met de FreeType of de ImageMagick PHP extensie is vereist.', 'Get a new code' => 'Nieuwe code opvragen', @@ -127,9 +125,11 @@ 'Map data must be an array or an object implementing Traversable.' => 'De Map data moet een array zijn, of een object dat Traversable implementeerd.', 'Missing the temporary folder to store the uploaded file "{file}".' => 'Er is geen tijdelijke map om het geuploade bestand te bewaren.', 'Next >' => 'Volgende >', + 'No' => 'Nee', 'No columns are being updated for table "{table}".' => 'Er worden geen kolommen geupdated voor tabel "{table}".', 'No counter columns are being updated for table "{table}".' => 'Er worden geen counter kolommen geupdated voor table "{table}".', 'Object configuration must be an array containing a "class" element.' => 'De configuratie voor een object moet een array zijn met een "class" element.', + 'Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.' => 'Bij het gebruik van PHP 5.1.1 of lager zijn alleen de MD5 en SHA1 hashing algoritmes ondersteund.', 'Please fix the following input errors:' => 'Gelieve de volgende fouten te verhelpen:', 'Powered by {yii}.' => 'Aangedreven door {yii}.', 'Property "{class}.{property}" is not defined.' => 'Het attribuut "{class}.{property}" is niet gedefinieerd.', @@ -139,6 +139,7 @@ 'Relation "{name}" is not defined in active record class "{class}".' => 'De relatie "{name}" is niet gedefinieerd in active record class "{class}".', 'Removing a primary key after table has been created is not supported by SQLite.' => 'SQLite kan geen primary key verwijderen nadat de tabel aangemaakt is.', 'Renaming a DB column is not supported by SQLite.' => 'Een kolom hernoemen wordt niet ondersteund door SQLite.', + 'Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".' => 'Voor "CClientScript::POS_LOAD" en "CClientScript::POS_READY" zijn geen script HTML opties toegelaten.', 'Stack data must be an array or an object implementing Traversable.' => 'Stack data moet een array zijn of een object dat Traversable implementeerd.', 'Table "{table}" does not exist.' => 'Tabel "{table}" bestaat niet.', 'Table "{table}" does not have a column named "{column}".' => 'Tabel "{table}" heeft geen kolom "{column}".', @@ -147,8 +148,8 @@ 'The "forceCopy" and "linkAssets" cannot be both true.' => '"forceCopy" en "linkAssets" kunnen niet beide de waard true hebben.', 'The "pattern" property must be specified with a valid regular expression.' => 'Het "pattern" attribuut moet een geldige reguliere expressie bevatten.', 'The "range" property must be specified with a list of values.' => 'Het "range" attribuut moet een lijst van waarden bevatten.', + 'The $converter argument must be a valid callback or null.' => 'De $converter parameter moet een geldige callback of null zijn', 'The CSRF token could not be verified.' => 'Het CSRF token kon niet geverifieerd worden.', - 'The DB query must contain the "from" portion.' => 'De DB query moet een "from"-gedeelte bevatten.', 'The STAT relation "{name}" cannot have child relations.' => 'De STAT relatie "{name}" kan geen onderliggende relaties hebben.', 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'Het URL pattern "{pattern}" voor route "{route}" is geen geldige reguliere expressie.', 'The active record cannot be deleted because it is new.' => 'Het active record kan niet verwijderd worden want het is nieuw.', @@ -187,7 +188,6 @@ 'The pattern for week in month must be "W".' => 'Het patroon voor week in maand moet "W" zijn.', 'The pattern for week in year must be "w".' => 'Het patroon voor week in jaar moet "w" zijn.', 'The queue is empty.' => 'De queue is leeg.', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'De relatie "{relation}" in active record class "{class}" is ongeldig. De join table "{joinTable}" uit de foreign key kan niet gevonden worden in de database.', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'De relatie "{relation}" in active record class "{class}" is ongeldig opgegeven: de join table "{joinTable}" uit de foreign key kan niet gevonden worden in de database.', 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'De relatie "{relation}" in active record class "{class}" bevat een foreign key "{key}" die niet naar de parent tabel "{table}" wijst.', 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'De relatie "{relation}" in active record class "{class}" bevat een onvolledige foreign key. De foreign key moet bestaan uit kolommen van beide tabellen.', @@ -208,6 +208,7 @@ 'Unable to change the item name. The name "{name}" is already used by another item.' => 'Kan de item naam niet wijzigen. De naam "{name}" is reeds in gebruik door een ander item.', 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'Kan het status bestand "{file}" voor de applicatie niet aanmaken. Zorg ervoor dat de map waarin het bestand zit bestaat en beschrijfbaar is voor de web server.', 'Unable to find "{column}" in table "{table}".' => 'Kan "{column}" niet vinden in tabel "{table}".', + 'Unable to generate random string.' => 'Kon geen willekeurige string aanmaken', 'Unable to lock file "{file}" for reading.' => 'Kan het bestand "{file}" niet vergrendelen voor lezen.', 'Unable to lock file "{file}" for writing.' => 'Kan het bestand "{file}" niet vergrendelen voor schrijven.', 'Unable to read file "{file}".' => 'Kan het bestand "{file}" niet lezen.', @@ -219,6 +220,7 @@ 'Unknown type "{type}".' => 'Onbekend type "{type}".', 'Unrecognized locale "{locale}".' => 'Onbekende localisatie "{locale}".', 'View file "{file}" does not exist.' => 'De view "{file}" bestaat niet.', + 'Yes' => 'Ja', 'Yii application can only be created once.' => 'Er kan slechts 1 Yii applicatie aangemaakt worden.', 'You are not authorized to perform this action.' => 'Je hebt geen toegang tot deze actie.', 'Your request is invalid.' => 'Je verzoek is ongeldig', diff --git a/framework/messages/nl/zii.php b/framework/messages/nl/zii.php index ef1a58b696..5f38d6e1a8 100644 --- a/framework/messages/nl/zii.php +++ b/framework/messages/nl/zii.php @@ -17,7 +17,7 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'The property filterSelector should be defined.' => 'Het attribuut filterSelector moet gedefinieerd zijn.', + '{class} must specify "model" and "{attribute}" or "{name}" property values.' => '{class} moet "model" en "{attribute}" of "{name}" waarden bevatten voor de properties.', 'Are you sure you want to delete this item?' => 'Ben je zeker dat je dit item wil verwijderen?', 'Delete' => 'Verwijderen', 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Getoond: {start}-{end} van 1 resultaat.|Getoond: {start}-{end} van {count} resultaten.', @@ -33,9 +33,9 @@ 'The button type "{type}" is not supported.' => 'Het button type "{type}" wordt niet ondersteund.', 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Het formaat van de kolom moet "Name:Type:Label" zijn, "Type" en "Label" zijn optioneel.', 'The property "itemView" cannot be empty.' => 'Het "itemView" property mag niet leeg zijn.', + 'The property filterSelector should be defined.' => 'Het attribuut filterSelector moet gedefinieerd zijn.', 'The property updateSelector should be defined.' => 'Het updateSelector property moet gedefinieerd zijn.', 'Total 1 result.|Total {count} results.' => '1 resultaat in totaal.|{count} resultaten in totaal.', 'Update' => 'Updaten', 'View' => 'Bekijken', - '{class} must specify "model" and "attribute" or "name" property values.' => '{class} moet de "model" en "attribute" of "name" property waarden bevatten.', ); diff --git a/framework/messages/no/zii.php b/framework/messages/no/zii.php new file mode 100644 index 0000000000..a9c5e2f3c4 --- /dev/null +++ b/framework/messages/no/zii.php @@ -0,0 +1,36 @@ + 'Er du sikker på at du vil slette dette elementet?', + 'Home' => 'Hjem', + 'The button type "{type}" is not supported.' => 'Knapp av typen "{type}" støttes ikke.', + 'Delete' => 'Slett', + 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Viser 1 av totalt 1 resultat.|Viser {start}-{end} av totalt {count} resultater.', + 'Either "name" or "value" must be specified for CDataColumn.' => 'Enten "name" eller "value" må oppgis for CDataColumn.', + 'No results found.' => 'Ingen resultat funnet', + 'Not set' => 'Ikke satt', + 'Please specify the "attributes" property.' => 'Vennligst spesifiser egenskapen "attributes".', + 'Please specify the "data" property.' => 'Vennligst spesifiser egneskapen "data".', + 'Sort by: ' => 'Sorter på: ', + 'The "dataProvider" property cannot be empty.' => 'Egenskapen "dataProvider" kan ikke være tom.', + 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Attributtet må angis på formatet "Name:Type:Label", der "Type" og "Label" kan utelates.', + 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Kollonnen må anigs på formatet "Name:Type:Label", der "Type" og "Label" kan utelates.', + 'The property "itemView" cannot be empty.' => 'Egenskapen "itemView" kan ikke være tom.', + 'Total 1 result.|Total {count} results.' => 'Totalt 1 resultat.|Totalt {count} resultater.', + 'Update' => 'Oppdater', + 'View' => 'Vis', + '{class} must specify "model" and "attribute" or "name" property values.' => '{class} må spesifisere verdier for egenskapene "model" og "attribute" eller "name".', +); diff --git a/framework/messages/ru/yii.php b/framework/messages/ru/yii.php index b816c6ad9c..156cd038ff 100644 --- a/framework/messages/ru/yii.php +++ b/framework/messages/ru/yii.php @@ -17,9 +17,9 @@ * NOTE, this file must be saved in UTF-8 encoding. */ return array ( - 'No' => 'Нет', - 'The $converter argument must be a valid callback or null.' => 'Аргумент $converter должен быть либо callback либо null.', - 'Yes' => 'Да', + 'A PHP extension stopped the file upload.' => 'PHP расширение прервало загрузку файла.', + 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.' => 'CSecurityManager::generateRandomString() не может сгенерировать случайную строку в текущем окружении.', + 'Unable to generate random string.' => 'Невозможно сгенерировать случайную строку.', '"{path}" is not a valid directory.' => 'Путь "{path}" не является правильной директорией.', '< Previous' => '< Предыдущая', '<< First' => '<< Первая', @@ -109,7 +109,6 @@ 'Failed to set unsafe attribute "{attribute}" of "{class}".' => 'Не удалось присвоить небезопасный атрибут "{attribute}" класса "{class}".', 'Failed to start session.' => 'Не удалось запустить сессию.', 'Failed to write the uploaded file "{file}" to disk.' => 'Не удалось записать загруженный файл "{file}" на диск.', - 'File upload was stopped by extension.' => 'Загрузка файла прервана расширением.', 'Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".' => 'Фильтр "{filter}" неверный. Контроллер "{class}" не содержит метода "filter{filter}".', 'GD with FreeType or ImageMagick PHP extensions are required.' => 'Требуются расширения PHP GD с FreeType или ImageMagick.', 'Get a new code' => 'Получить новый код', @@ -128,6 +127,7 @@ 'Map data must be an array or an object implementing Traversable.' => 'Карта должна быть представлена массивом или объектом, реализующим интерфейс Traversable.', 'Missing the temporary folder to store the uploaded file "{file}".' => 'Не найдена временная директория для хранения загруженного файла "{file}".', 'Next >' => 'Следующая >', + 'No' => 'Нет', 'No columns are being updated for table "{table}".' => 'Нет столбцов подлежащих обновлению в таблице "{table}".', 'No counter columns are being updated for table "{table}".' => 'Нет столбцов-счетчиков подлежащих обновлению в таблице "{table}".', 'Object configuration must be an array containing a "class" element.' => 'Конфигурация объекта должна быть представлена массивом, содержащим элемент "class".', @@ -150,6 +150,7 @@ 'The "forceCopy" and "linkAssets" cannot be both true.' => '"forceCopy" и "linkAssets" не могут быть true одновременно.', 'The "pattern" property must be specified with a valid regular expression.' => 'Свойство "pattern" должно быть определено правильным регулярным выражением.', 'The "range" property must be specified with a list of values.' => 'Свойство "range" должно указываться со списком значений.', + 'The $converter argument must be a valid callback or null.' => 'Аргумент $converter должен быть либо callback либо null.', 'The CSRF token could not be verified.' => 'Невозможно определить CSRF.', 'The DB query must contain the "from" portion.' => 'Запрос к БД должен содержать "from".', 'The STAT relation "{name}" cannot have child relations.' => 'Отношение STAT "{name}" не может содержать другие отношения.', @@ -190,7 +191,6 @@ 'The pattern for week in month must be "W".' => 'Шаблон недели в месяце: "W".', 'The pattern for week in year must be "w".' => 'Шаблон недели в году: "w".', 'The queue is empty.' => 'Очередь пуста.', - 'The relation "{relation}" in active record class "{class}" is not specified correctly. The join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Отношение "{relation}" AR модели "{class}" определено некорректно. Таблица "{joinTable}", указанная во внешнем ключе, не найдена в БД.', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.' => 'Отношение "{relation}", определенное в записи active record класса "{class}" ошибочно: в базе данных нет включаемой таблицы "{joinTable}", упомянутой во внешнем ключе.', 'The relation "{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".' => 'Отношение "{relation}" AR модели "{class}" определено с использованием внешнего ключа "{key}", который не указывает на таблицу-родителя "{table}".', 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.' => 'Отношение "{relation}", определенное в записи active record класса "{class}", определено неполным внешним ключом. Внешний ключ должен состоять из столбцов, относящихся к обоим объединяемым таблицам.', @@ -222,6 +222,7 @@ 'Unknown type "{type}".' => 'Неизвестный тип "{type}".', 'Unrecognized locale "{locale}".' => 'Неизвестная локаль "{locale}".', 'View file "{file}" does not exist.' => 'Файл представления "{file}" не существует.', + 'Yes' => 'Да', 'Yii application can only be created once.' => 'Приложение Yii может быть создано только один раз.', 'You are not authorized to perform this action.' => 'У вас недостаточно прав для выполнения указанного действия.', 'Your request is invalid.' => 'Некорректный запрос.', diff --git a/framework/utils/CFileHelper.php b/framework/utils/CFileHelper.php index 16272339d8..0a8ae3fb01 100644 --- a/framework/utils/CFileHelper.php +++ b/framework/utils/CFileHelper.php @@ -58,7 +58,7 @@ public static function copyDirectory($src,$dst,$options=array()) $level=-1; extract($options); if(!is_dir($dst)) - self::mkdir($dst,$options,true); + self::createDirectory($dst,isset($options['newDirMode'])?$options['newDirMode']:null,true); self::copyDirectoryRecursive($src,$dst,'',$fileTypes,$exclude,$level,$options); } @@ -100,6 +100,7 @@ public static function removeDirectory($directory) * Level 0 means searching for only the files DIRECTLY under the directory; * level N means searching for those directories that are within N levels. * + *
    • absolutePaths: boolean, whether to return absolute paths or relative ones, defaults to true.
    • *
    * @return array files found under the directory. The file list is sorted. */ @@ -108,8 +109,9 @@ public static function findFiles($dir,$options=array()) $fileTypes=array(); $exclude=array(); $level=-1; + $absolutePaths=true; extract($options); - $list=self::findFilesRecursive($dir,'',$fileTypes,$exclude,$level); + $list=self::findFilesRecursive($dir,'',$fileTypes,$exclude,$level,$absolutePaths); sort($list); return $list; } @@ -136,9 +138,11 @@ public static function findFiles($dir,$options=array()) protected static function copyDirectoryRecursive($src,$dst,$base,$fileTypes,$exclude,$level,$options) { if(!is_dir($dst)) - self::mkdir($dst,$options,false); + self::createDirectory($dst,isset($options['newDirMode'])?$options['newDirMode']:null,false); $folder=opendir($src); + if($folder===false) + throw new Exception('Unable to open directory: ' . $src); while(($file=readdir($folder))!==false) { if($file==='.' || $file==='..') @@ -174,24 +178,28 @@ protected static function copyDirectoryRecursive($src,$dst,$base,$fileTypes,$exc * Level -1 means searching for all directories and files under the directory; * Level 0 means searching for only the files DIRECTLY under the directory; * level N means searching for those directories that are within N levels. + * @param boolean $absolutePaths whether to return absolute paths or relative ones * @return array files found under the directory. */ - protected static function findFilesRecursive($dir,$base,$fileTypes,$exclude,$level) + protected static function findFilesRecursive($dir,$base,$fileTypes,$exclude,$level,$absolutePaths) { $list=array(); - $handle=opendir($dir); + $handle=opendir($dir.$base); + if($handle===false) + throw new Exception('Unable to open directory: ' . $dir); while(($file=readdir($handle))!==false) { if($file==='.' || $file==='..') continue; - $path=$dir.DIRECTORY_SEPARATOR.$file; - $isFile=is_file($path); + $path=substr($base.DIRECTORY_SEPARATOR.$file,1); + $fullPath=$dir.DIRECTORY_SEPARATOR.$path; + $isFile=is_file($fullPath); if(self::validatePath($base,$file,$isFile,$fileTypes,$exclude)) { if($isFile) - $list[]=$path; + $list[]=$absolutePaths?$fullPath:$path; elseif($level) - $list=array_merge($list,self::findFilesRecursive($path,$base.'/'.$file,$fileTypes,$exclude,$level-1)); + $list=array_merge($list,self::findFilesRecursive($dir,$base.'/'.$file,$fileTypes,$exclude,$level-1,$absolutePaths)); } } closedir($handle); @@ -292,18 +300,18 @@ public static function getMimeTypeByExtension($file,$magicFile=null) * For avoidance of umask side-effects chmod is used. * * @param string $dst path to be created - * @param array $options newDirMode element used, must contain access bitmask + * @param integer $mode the permission to be set for newly created directories, if not set - 0777 will be used * @param boolean $recursive whether to create directory structure recursive if parent dirs do not exist * @return boolean result of mkdir * @see mkdir */ - private static function mkdir($dst,array $options,$recursive) + public static function createDirectory($dst,$mode=null,$recursive=false) { + if($mode===null) + $mode=0777; $prevDir=dirname($dst); if($recursive && !is_dir($dst) && !is_dir($prevDir)) - self::mkdir(dirname($dst),$options,true); - - $mode=isset($options['newDirMode']) ? $options['newDirMode'] : 0777; + self::createDirectory(dirname($dst),$mode,true); $res=mkdir($dst, $mode); @chmod($dst,$mode); return $res; diff --git a/framework/utils/CFormatter.php b/framework/utils/CFormatter.php index f072c366a7..4fbebb65e0 100644 --- a/framework/utils/CFormatter.php +++ b/framework/utils/CFormatter.php @@ -234,7 +234,10 @@ protected function normalizeDateValue($time) else return strtotime($time); } - return (int)$time; + elseif (class_exists('DateTime', false) && $time instanceof DateTime) + return $time->getTimestamp(); + else + return (int)$time; } /** diff --git a/framework/utils/CLocalizedFormatter.php b/framework/utils/CLocalizedFormatter.php index a2323f1804..b9df0bcb30 100644 --- a/framework/utils/CLocalizedFormatter.php +++ b/framework/utils/CLocalizedFormatter.php @@ -14,7 +14,7 @@ * It provides the same functionality as {@link CFormatter}, but overrides all the settings for * {@link booleanFormat}, {@link datetimeFormat} and {@link numberFormat} with the values for the * current locale. Because of this you are not able to configure these properties for CLocalizedFormatter directly. - * Date and time format can be adjsuted by setting {@link dateFormat} and {@link timeFormat}. + * Date and time format can be adjusted by setting {@link dateFormat} and {@link timeFormat}. * * It uses {@link CApplication::locale} by default but you can set a custom locale by using {@link setLocale}-method. * diff --git a/framework/utils/CPasswordHelper.php b/framework/utils/CPasswordHelper.php index 553bdc624d..1949470d0b 100644 --- a/framework/utils/CPasswordHelper.php +++ b/framework/utils/CPasswordHelper.php @@ -40,7 +40,7 @@ * * To verify a password, fetch the user's saved hash from the database (into $hash) and: *
    - * if (CPasswordHelper::verifyPassword($password, $hash)
    + * if (CPasswordHelper::verifyPassword($password, $hash))
      *     // password is good
      * else
      *     // password is bad
    @@ -102,7 +102,7 @@ public static function hashPassword($password,$cost=13)
     	/**
     	 * Verify a password against a hash.
     	 *
    -	 * @param string $password The password to verify.
    +	 * @param string $password The password to verify. If password is empty or not a string, method will return false.
     	 * @param string $hash The hash to verify the password against.
     	 * @return bool True if the password matches the hash.
     	 * @throws CException on bad password or hash parameters or if crypt() with Blowfish hash is not available.
    @@ -111,7 +111,7 @@ public static function verifyPassword($password, $hash)
     	{
     		self::checkBlowfish();
     		if(!is_string($password) || $password==='')
    -			throw new CException(Yii::t('yii','Cannot hash a password that is empty or not a string.'));
    +			return false;
     
     		if (!$password || !preg_match('{^\$2[axy]\$(\d\d)\$[\./0-9A-Za-z]{22}}',$hash,$matches) ||
     			$matches[1]<4 || $matches[1]>31)
    diff --git a/framework/validators/CFileValidator.php b/framework/validators/CFileValidator.php
    index b74d104a50..2d45df806a 100644
    --- a/framework/validators/CFileValidator.php
    +++ b/framework/validators/CFileValidator.php
    @@ -183,26 +183,34 @@ protected function validateAttribute($object, $attribute)
     	 */
     	protected function validateFile($object, $attribute, $file)
     	{
    -		if(null===$file || ($error=$file->getError())==UPLOAD_ERR_NO_FILE)
    -			return $this->emptyAttribute($object, $attribute);
    -		elseif($error==UPLOAD_ERR_INI_SIZE || $error==UPLOAD_ERR_FORM_SIZE || $this->maxSize!==null && $file->getSize()>$this->maxSize)
    +		$error=(null===$file ? null : $file->getError());
    +		if($error==UPLOAD_ERR_INI_SIZE || $error==UPLOAD_ERR_FORM_SIZE || $this->maxSize!==null && $file->getSize()>$this->maxSize)
     		{
     			$message=$this->tooLarge!==null?$this->tooLarge : Yii::t('yii','The file "{file}" is too large. Its size cannot exceed {limit} bytes.');
    -			$this->addError($object,$attribute,$message,array('{file}'=>$file->getName(), '{limit}'=>$this->getSizeLimit()));
    +			$this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{limit}'=>$this->getSizeLimit()));
    +			if($error!==UPLOAD_ERR_OK)
    +				return;
    +		}
    +		elseif($error!==UPLOAD_ERR_OK)
    +		{
    +			if($error==UPLOAD_ERR_NO_FILE)
    +				return $this->emptyAttribute($object, $attribute);
    +			elseif($error==UPLOAD_ERR_PARTIAL)
    +				throw new CException(Yii::t('yii','The file "{file}" was only partially uploaded.',array('{file}'=>CHtml::encode($file->getName()))));
    +			elseif($error==UPLOAD_ERR_NO_TMP_DIR)
    +				throw new CException(Yii::t('yii','Missing the temporary folder to store the uploaded file "{file}".',array('{file}'=>CHtml::encode($file->getName()))));
    +			elseif($error==UPLOAD_ERR_CANT_WRITE)
    +				throw new CException(Yii::t('yii','Failed to write the uploaded file "{file}" to disk.',array('{file}'=>CHtml::encode($file->getName()))));
    +			elseif(defined('UPLOAD_ERR_EXTENSION') && $error==UPLOAD_ERR_EXTENSION)  // available for PHP 5.2.0 or above
    +				throw new CException(Yii::t('yii','A PHP extension stopped the file upload.'));
    +			else
    +				throw new CException(Yii::t('yii','Unable to upload the file "{file}" because of an unrecognized error.',array('{file}'=>CHtml::encode($file->getName()))));
     		}
    -		elseif($error==UPLOAD_ERR_PARTIAL)
    -			throw new CException(Yii::t('yii','The file "{file}" was only partially uploaded.',array('{file}'=>$file->getName())));
    -		elseif($error==UPLOAD_ERR_NO_TMP_DIR)
    -			throw new CException(Yii::t('yii','Missing the temporary folder to store the uploaded file "{file}".',array('{file}'=>$file->getName())));
    -		elseif($error==UPLOAD_ERR_CANT_WRITE)
    -			throw new CException(Yii::t('yii','Failed to write the uploaded file "{file}" to disk.',array('{file}'=>$file->getName())));
    -		elseif(defined('UPLOAD_ERR_EXTENSION') && $error==UPLOAD_ERR_EXTENSION)  // available for PHP 5.2.0 or above
    -			throw new CException(Yii::t('yii','File upload was stopped by extension.'));
     
     		if($this->minSize!==null && $file->getSize()<$this->minSize)
     		{
     			$message=$this->tooSmall!==null?$this->tooSmall : Yii::t('yii','The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.');
    -			$this->addError($object,$attribute,$message,array('{file}'=>$file->getName(), '{limit}'=>$this->minSize));
    +			$this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{limit}'=>$this->minSize));
     		}
     
     		if($this->types!==null)
    @@ -214,11 +222,11 @@ protected function validateFile($object, $attribute, $file)
     			if(!in_array(strtolower($file->getExtensionName()),$types))
     			{
     				$message=$this->wrongType!==null?$this->wrongType : Yii::t('yii','The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.');
    -				$this->addError($object,$attribute,$message,array('{file}'=>$file->getName(), '{extensions}'=>implode(', ',$types)));
    +				$this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{extensions}'=>implode(', ',$types)));
     			}
     		}
     
    -		if($this->mimeTypes!==null)
    +		if($this->mimeTypes!==null && !empty($file->tempName))
     		{
     			if(function_exists('finfo_open'))
     			{
    @@ -239,7 +247,7 @@ protected function validateFile($object, $attribute, $file)
     			if($mimeType===false || !in_array(strtolower($mimeType),$mimeTypes))
     			{
     				$message=$this->wrongMimeType!==null?$this->wrongMimeType : Yii::t('yii','The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.');
    -				$this->addError($object,$attribute,$message,array('{file}'=>$file->getName(), '{mimeTypes}'=>implode(', ',$mimeTypes)));
    +				$this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{mimeTypes}'=>implode(', ',$mimeTypes)));
     			}
     		}
     	}
    diff --git a/framework/validators/CValidator.php b/framework/validators/CValidator.php
    index 84ae6431f3..c0398d76e0 100644
    --- a/framework/validators/CValidator.php
    +++ b/framework/validators/CValidator.php
    @@ -141,7 +141,7 @@ abstract protected function validateAttribute($object,$attribute);
     	public static function createValidator($name,$object,$attributes,$params=array())
     	{
     		if(is_string($attributes))
    -			$attributes=preg_split('/[\s,]+/',$attributes,-1,PREG_SPLIT_NO_EMPTY);
    +			$attributes=preg_split('/\s*,\s*/',$attributes,-1,PREG_SPLIT_NO_EMPTY);
     
     		if(isset($params['on']))
     		{
    diff --git a/framework/vendors/README.html b/framework/vendors/README.html
    index 4162ec9311..898b756a18 100644
    --- a/framework/vendors/README.html
    +++ b/framework/vendors/README.html
    @@ -100,7 +100,7 @@ 

    Third-Party Library List

    Net_IDNA2 - Punycode encoding and decoding (0.1.1 (beta) was released on 2010-12-09) - LGPL + LGPL CUrlValidator, CEmailValidator diff --git a/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php b/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php index f440202b4b..64dbf10696 100644 --- a/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php +++ b/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php @@ -7,7 +7,7 @@ * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS * FILE, changes will be overwritten the next time the script is run. * - * @version 4.5.0 + * @version 4.6.0 * * @warning * You must *not* include any other HTML Purifier files before this file, @@ -39,7 +39,7 @@ */ /* - HTML Purifier 4.5.0 - Standards Compliant HTML Filtering + HTML Purifier 4.6.0 - Standards Compliant HTML Filtering Copyright (C) 2006-2008 Edward Z. Yang This library is free software; you can redistribute it and/or @@ -74,66 +74,97 @@ class HTMLPurifier { - /** Version of HTML Purifier */ - public $version = '4.5.0'; + /** + * Version of HTML Purifier. + * @type string + */ + public $version = '4.6.0'; - /** Constant with version of HTML Purifier */ - const VERSION = '4.5.0'; + /** + * Constant with version of HTML Purifier. + */ + const VERSION = '4.6.0'; - /** Global configuration object */ + /** + * Global configuration object. + * @type HTMLPurifier_Config + */ public $config; - /** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */ + /** + * Array of extra filter objects to run on HTML, + * for backwards compatibility. + * @type HTMLPurifier_Filter[] + */ private $filters = array(); - /** Single instance of HTML Purifier */ + /** + * Single instance of HTML Purifier. + * @type HTMLPurifier + */ private static $instance; - protected $strategy, $generator; + /** + * @type HTMLPurifier_Strategy_Core + */ + protected $strategy; + + /** + * @type HTMLPurifier_Generator + */ + protected $generator; /** - * Resultant HTMLPurifier_Context of last run purification. Is an array - * of contexts if the last called method was purifyArray(). + * Resultant context of last run purification. + * Is an array of contexts if the last called method was purifyArray(). + * @type HTMLPurifier_Context */ public $context; /** * Initializes the purifier. - * @param $config Optional HTMLPurifier_Config object for all instances of - * the purifier, if omitted, a default configuration is - * supplied (which can be overridden on a per-use basis). + * + * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object + * for all instances of the purifier, if omitted, a default + * configuration is supplied (which can be overridden on a + * per-use basis). * The parameter can also be any type that * HTMLPurifier_Config::create() supports. */ - public function __construct($config = null) { - + public function __construct($config = null) + { $this->config = HTMLPurifier_Config::create($config); - - $this->strategy = new HTMLPurifier_Strategy_Core(); - + $this->strategy = new HTMLPurifier_Strategy_Core(); } /** * Adds a filter to process the output. First come first serve - * @param $filter HTMLPurifier_Filter object + * + * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object */ - public function addFilter($filter) { - trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING); + public function addFilter($filter) + { + trigger_error( + 'HTMLPurifier->addFilter() is deprecated, use configuration directives' . + ' in the Filter namespace or Filter.Custom', + E_USER_WARNING + ); $this->filters[] = $filter; } /** * Filters an HTML snippet/document to be XSS-free and standards-compliant. * - * @param $html String of HTML to purify - * @param $config HTMLPurifier_Config object for this operation, if omitted, - * defaults to the config object specified during this + * @param string $html String of HTML to purify + * @param HTMLPurifier_Config $config Config object for this operation, + * if omitted, defaults to the config object specified during this * object's construction. The parameter can also be any type * that HTMLPurifier_Config::create() supports. - * @return Purified HTML + * + * @return string Purified HTML */ - public function purify($html, $config = null) { - + public function purify($html, $config = null) + { // :TODO: make the config merge in, instead of replace $config = $config ? HTMLPurifier_Config::create($config) : $this->config; @@ -171,8 +202,12 @@ public function purify($html, $config = null) { unset($filter_flags['Custom']); $filters = array(); foreach ($filter_flags as $filter => $flag) { - if (!$flag) continue; - if (strpos($filter, '.') !== false) continue; + if (!$flag) { + continue; + } + if (strpos($filter, '.') !== false) { + continue; + } $class = "HTMLPurifier_Filter_$filter"; $filters[] = new $class; } @@ -195,9 +230,12 @@ public function purify($html, $config = null) { // list of un-purified tokens $lexer->tokenizeHTML( // un-purified HTML - $html, $config, $context + $html, + $config, + $context ), - $config, $context + $config, + $context ) ); @@ -212,11 +250,15 @@ public function purify($html, $config = null) { /** * Filters an array of HTML snippets - * @param $config Optional HTMLPurifier_Config object for this operation. + * + * @param string[] $array_of_html Array of html snippets + * @param HTMLPurifier_Config $config Optional config object for this operation. * See HTMLPurifier::purify() for more details. - * @return Array of purified HTML + * + * @return string[] Array of purified HTML */ - public function purifyArray($array_of_html, $config = null) { + public function purifyArray($array_of_html, $config = null) + { $context_array = array(); foreach ($array_of_html as $key => $html) { $array_of_html[$key] = $this->purify($html, $config); @@ -228,11 +270,16 @@ public function purifyArray($array_of_html, $config = null) { /** * Singleton for enforcing just one HTML Purifier in your system - * @param $prototype Optional prototype HTMLPurifier instance to - * overload singleton with, or HTMLPurifier_Config - * instance to configure the generated version with. + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { if (!self::$instance || $prototype) { if ($prototype instanceof HTMLPurifier) { self::$instance = $prototype; @@ -246,18 +293,98 @@ public static function instance($prototype = null) { } /** + * Singleton for enforcing just one HTML Purifier in your system + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier * @note Backwards compatibility, see instance() */ - public static function getInstance($prototype = null) { + public static function getInstance($prototype = null) + { return HTMLPurifier::instance($prototype); } - } +/** + * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node, + * and back again. + * + * @note This transformation is not an equivalence. We mutate the input + * token stream to make it so; see all [MUT] markers in code. + */ +class HTMLPurifier_Arborize +{ + public static function arborize($tokens, $config, $context) { + $definition = $config->getHTMLDefinition(); + $parent = new HTMLPurifier_Token_Start($definition->info_parent); + $stack = array($parent->toNode()); + foreach ($tokens as $token) { + $token->skip = null; // [MUT] + $token->carryover = null; // [MUT] + if ($token instanceof HTMLPurifier_Token_End) { + $token->start = null; // [MUT] + $r = array_pop($stack); + assert($r->name === $token->name); + assert(empty($token->attr)); + $r->endCol = $token->col; + $r->endLine = $token->line; + $r->endArmor = $token->armor; + continue; + } + $node = $token->toNode(); + $stack[count($stack)-1]->children[] = $node; + if ($token instanceof HTMLPurifier_Token_Start) { + $stack[] = $node; + } + } + assert(count($stack) == 1); + return $stack[0]; + } + + public static function flatten($node, $config, $context) { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingTokens = array(); + $tokens = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + list($start, $end) = $node->toTokenPair(); + if ($level > 0) { + $tokens[] = $start; + } + if ($end !== NULL) { + $closingTokens[$level][] = $end; + } + if ($node instanceof HTMLPurifier_Node_Element) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->children as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingTokens[$level])) { + while ($token = array_pop($closingTokens[$level])) { + $tokens[] = $token; + } + } + } while ($level > 0); + return $tokens; + } +} + + + /** * Defines common attribute collections that modules reference */ @@ -266,7 +393,8 @@ class HTMLPurifier_AttrCollections { /** - * Associative array of attribute collections, indexed by name + * Associative array of attribute collections, indexed by name. + * @type array */ public $info = array(); @@ -274,10 +402,11 @@ class HTMLPurifier_AttrCollections * Performs all expansions on internal data for use by other inclusions * It also collects all attribute collection extensions from * modules - * @param $attr_types HTMLPurifier_AttrTypes instance - * @param $modules Hash array of HTMLPurifier_HTMLModule members + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members */ - public function __construct($attr_types, $modules) { + public function __construct($attr_types, $modules) + { // load extensions from the modules foreach ($modules as $module) { foreach ($module->attr_collections as $coll_i => $coll) { @@ -288,7 +417,9 @@ public function __construct($attr_types, $modules) { if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) { // merge in includes $this->info[$coll_i][$attr_i] = array_merge( - $this->info[$coll_i][$attr_i], $attr); + $this->info[$coll_i][$attr_i], + $attr + ); continue; } $this->info[$coll_i][$attr_i] = $attr; @@ -307,20 +438,29 @@ public function __construct($attr_types, $modules) { /** * Takes a reference to an attribute associative array and performs * all inclusions specified by the zero index. - * @param &$attr Reference to attribute array + * @param array &$attr Reference to attribute array */ - public function performInclusions(&$attr) { - if (!isset($attr[0])) return; + public function performInclusions(&$attr) + { + if (!isset($attr[0])) { + return; + } $merge = $attr[0]; $seen = array(); // recursion guard // loop through all the inclusions for ($i = 0; isset($merge[$i]); $i++) { - if (isset($seen[$merge[$i]])) continue; + if (isset($seen[$merge[$i]])) { + continue; + } $seen[$merge[$i]] = true; // foreach attribute of the inclusion, copy it over - if (!isset($this->info[$merge[$i]])) continue; + if (!isset($this->info[$merge[$i]])) { + continue; + } foreach ($this->info[$merge[$i]] as $key => $value) { - if (isset($attr[$key])) continue; // also catches more inclusions + if (isset($attr[$key])) { + continue; + } // also catches more inclusions $attr[$key] = $value; } if (isset($this->info[$merge[$i]][0])) { @@ -334,20 +474,24 @@ public function performInclusions(&$attr) { /** * Expands all string identifiers in an attribute array by replacing * them with the appropriate values inside HTMLPurifier_AttrTypes - * @param &$attr Reference to attribute array - * @param $attr_types HTMLPurifier_AttrTypes instance + * @param array &$attr Reference to attribute array + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance */ - public function expandIdentifiers(&$attr, $attr_types) { - + public function expandIdentifiers(&$attr, $attr_types) + { // because foreach will process new elements we add, make sure we // skip duplicates $processed = array(); foreach ($attr as $def_i => $def) { // skip inclusions - if ($def_i === 0) continue; + if ($def_i === 0) { + continue; + } - if (isset($processed[$def_i])) continue; + if (isset($processed[$def_i])) { + continue; + } // determine whether or not attribute is required if ($required = (strpos($def_i, '*') !== false)) { @@ -378,9 +522,7 @@ public function expandIdentifiers(&$attr, $attr_types) { unset($attr[$def_i]); } } - } - } @@ -401,23 +543,25 @@ abstract class HTMLPurifier_AttrDef { /** - * Tells us whether or not an HTML attribute is minimized. Has no - * meaning in other contexts. + * Tells us whether or not an HTML attribute is minimized. + * Has no meaning in other contexts. + * @type bool */ public $minimized = false; /** - * Tells us whether or not an HTML attribute is required. Has no - * meaning in other contexts + * Tells us whether or not an HTML attribute is required. + * Has no meaning in other contexts + * @type bool */ public $required = false; /** * Validates and cleans passed string according to a definition. * - * @param $string String to be validated and cleaned. - * @param $config Mandatory HTMLPurifier_Config object. - * @param $context Mandatory HTMLPurifier_AttrContext object. + * @param string $string String to be validated and cleaned. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object. */ abstract public function validate($string, $config, $context); @@ -442,7 +586,8 @@ abstract public function validate($string, $config, $context); * parsing XML, thus, this behavior may still be correct. We * assume that newlines have been normalized. */ - public function parseCDATA($string) { + public function parseCDATA($string) + { $string = trim($string); $string = str_replace(array("\n", "\t", "\r"), ' ', $string); return $string; @@ -450,10 +595,11 @@ public function parseCDATA($string) { /** * Factory method for creating this class from a string. - * @param $string String construction info - * @return Created AttrDef object corresponding to $string + * @param string $string String construction info + * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string */ - public function make($string) { + public function make($string) + { // default implementation, return a flyweight of this object. // If $string has an effect on the returned object (i.e. you // need to overload this method), it is best @@ -464,16 +610,20 @@ public function make($string) { /** * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work * properly. THIS IS A HACK! + * @param string $string a CSS colour definition + * @return string */ - protected function mungeRgb($string) { + protected function mungeRgb($string) + { return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string); } /** - * Parses a possibly escaped CSS string and returns the "pure" + * Parses a possibly escaped CSS string and returns the "pure" * version of it. */ - protected function expandCSSEscape($string) { + protected function expandCSSEscape($string) + { // flexibly parse it $ret = ''; for ($i = 0, $c = strlen($string); $i < $c; $i++) { @@ -486,25 +636,32 @@ protected function expandCSSEscape($string) { if (ctype_xdigit($string[$i])) { $code = $string[$i]; for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { - if (!ctype_xdigit($string[$i])) break; + if (!ctype_xdigit($string[$i])) { + break; + } $code .= $string[$i]; } // We have to be extremely careful when adding // new characters, to make sure we're not breaking // the encoding. $char = HTMLPurifier_Encoder::unichr(hexdec($code)); - if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue; + if (HTMLPurifier_Encoder::cleanUTF8($char) === '') { + continue; + } $ret .= $char; - if ($i < $c && trim($string[$i]) !== '') $i--; + if ($i < $c && trim($string[$i]) !== '') { + $i--; + } + continue; + } + if ($string[$i] === "\n") { continue; } - if ($string[$i] === "\n") continue; } $ret .= $string[$i]; } return $ret; } - } @@ -531,37 +688,41 @@ abstract class HTMLPurifier_AttrTransform /** * Abstract: makes changes to the attributes dependent on multiple values. * - * @param $attr Assoc array of attributes, usually from + * @param array $attr Assoc array of attributes, usually from * HTMLPurifier_Token_Tag::$attr - * @param $config Mandatory HTMLPurifier_Config object. - * @param $context Mandatory HTMLPurifier_Context object - * @returns Processed attribute array. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object + * @return array Processed attribute array. */ abstract public function transform($attr, $config, $context); /** * Prepends CSS properties to the style attribute, creating the * attribute if it doesn't exist. - * @param $attr Attribute array to process (passed by reference) - * @param $css CSS to prepend + * @param array &$attr Attribute array to process (passed by reference) + * @param string $css CSS to prepend */ - public function prependCSS(&$attr, $css) { + public function prependCSS(&$attr, $css) + { $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; $attr['style'] = $css . $attr['style']; } /** * Retrieves and removes an attribute - * @param $attr Attribute array to process (passed by reference) - * @param $key Key of attribute to confiscate + * @param array &$attr Attribute array to process (passed by reference) + * @param mixed $key Key of attribute to confiscate + * @return mixed */ - public function confiscateAttr(&$attr, $key) { - if (!isset($attr[$key])) return null; + public function confiscateAttr(&$attr, $key) + { + if (!isset($attr[$key])) { + return null; + } $value = $attr[$key]; unset($attr[$key]); return $value; } - } @@ -574,7 +735,8 @@ public function confiscateAttr(&$attr, $key) { class HTMLPurifier_AttrTypes { /** - * Lookup array of attribute string identifiers to concrete implementations + * Lookup array of attribute string identifiers to concrete implementations. + * @type HTMLPurifier_AttrDef[] */ protected $info = array(); @@ -582,7 +744,8 @@ class HTMLPurifier_AttrTypes * Constructs the info array, supplying default implementations for attribute * types. */ - public function __construct() { + public function __construct() + { // XXX This is kind of poor, since we don't actually /clone/ // instances; instead, we use the supplied make() attribute. So, // the underlying class must know how to deal with arguments. @@ -622,36 +785,39 @@ public function __construct() { $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); } - private static function makeEnum($in) { + private static function makeEnum($in) + { return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); } /** * Retrieves a type - * @param $type String type name - * @return Object AttrDef for type + * @param string $type String type name + * @return HTMLPurifier_AttrDef Object AttrDef for type */ - public function get($type) { - + public function get($type) + { // determine if there is any extra info tacked on - if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2); - else $string = ''; + if (strpos($type, '#') !== false) { + list($type, $string) = explode('#', $type, 2); + } else { + $string = ''; + } if (!isset($this->info[$type])) { trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR); return; } - return $this->info[$type]->make($string); - } /** * Sets a new implementation for a type - * @param $type String type name - * @param $impl Object AttrDef for type + * @param string $type String type name + * @param HTMLPurifier_AttrDef $impl Object AttrDef for type */ - public function set($type, $impl) { + public function set($type, $impl) + { $this->info[$type] = $impl; } } @@ -669,17 +835,14 @@ class HTMLPurifier_AttrValidator { /** - * Validates the attributes of a token, returning a modified token + * Validates the attributes of a token, mutating it as necessary. * that has valid tokens - * @param $token Reference to token to validate. We require a reference - * because the operation this class performs on the token are - * not atomic, so the context CurrentToken to be updated - * throughout - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context + * @param HTMLPurifier_Token $token Token to validate. + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context */ - public function validateToken(&$token, &$config, $context) { - + public function validateToken($token, $config, $context) + { $definition = $config->getHTMLDefinition(); $e =& $context->get('ErrorCollector', true); @@ -692,12 +855,15 @@ public function validateToken(&$token, &$config, $context) { // initialize CurrentToken if necessary $current_token =& $context->get('CurrentToken', true); - if (!$current_token) $context->register('CurrentToken', $token); + if (!$current_token) { + $context->register('CurrentToken', $token); + } - if ( - !$token instanceof HTMLPurifier_Token_Start && + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty - ) return $token; + ) { + return; + } // create alias to global definition array, see also $defs // DEFINITION CALL @@ -711,7 +877,9 @@ public function validateToken(&$token, &$config, $context) { foreach ($definition->info_attr_transform_pre as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } @@ -720,7 +888,9 @@ public function validateToken(&$token, &$config, $context) { foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } @@ -737,7 +907,7 @@ public function validateToken(&$token, &$config, $context) { foreach ($attr as $attr_key => $value) { // call the definition - if ( isset($defs[$attr_key]) ) { + if (isset($defs[$attr_key])) { // there is a local definition defined if ($defs[$attr_key] === false) { // We've explicitly been told not to allow this element. @@ -749,15 +919,19 @@ public function validateToken(&$token, &$config, $context) { } else { // validate according to the element's definition $result = $defs[$attr_key]->validate( - $value, $config, $context - ); + $value, + $config, + $context + ); } - } elseif ( isset($d_defs[$attr_key]) ) { + } elseif (isset($d_defs[$attr_key])) { // there is a global definition defined, validate according // to the global definition $result = $d_defs[$attr_key]->validate( - $value, $config, $context - ); + $value, + $config, + $context + ); } else { // system never heard of the attribute? DELETE! $result = false; @@ -767,7 +941,9 @@ public function validateToken(&$token, &$config, $context) { if ($result === false || $result === null) { // this is a generic error message that should replaced // with more specific ones when possible - if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + if ($e) { + $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + } // remove the attribute unset($attr[$attr_key]); @@ -797,7 +973,9 @@ public function validateToken(&$token, &$config, $context) { foreach ($definition->info_attr_transform_post as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } @@ -805,14 +983,18 @@ public function validateToken(&$token, &$config, $context) { foreach ($definition->info[$token->name]->attr_transform_post as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } $token->attr = $attr; // destroy CurrentToken if we made it ourselves - if (!$current_token) $context->destroy('CurrentToken'); + if (!$current_token) { + $context->destroy('CurrentToken'); + } } @@ -856,11 +1038,15 @@ class HTMLPurifier_Bootstrap /** * Autoload function for HTML Purifier - * @param $class Class to load + * @param string $class Class to load + * @return bool */ - public static function autoload($class) { + public static function autoload($class) + { $file = HTMLPurifier_Bootstrap::getPath($class); - if (!$file) return false; + if (!$file) { + return false; + } // Technically speaking, it should be ok and more efficient to // just do 'require', but Antonio Parraga reports that with // Zend extensions such as Zend debugger and APC, this invariant @@ -872,9 +1058,14 @@ public static function autoload($class) { /** * Returns the path for a specific class. + * @param string $class Class path to get + * @return string */ - public static function getPath($class) { - if (strncmp('HTMLPurifier', $class, 12) !== 0) return false; + public static function getPath($class) + { + if (strncmp('HTMLPurifier', $class, 12) !== 0) { + return false; + } // Custom implementations if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { $code = str_replace('_', '-', substr($class, 22)); @@ -882,16 +1073,19 @@ public static function getPath($class) { } else { $file = str_replace('_', '/', $class) . '.php'; } - if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false; + if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { + return false; + } return $file; } /** * "Pre-registers" our autoloader on the SPL stack. */ - public static function registerAutoload() { + public static function registerAutoload() + { $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); - if ( ($funcs = spl_autoload_functions()) === false ) { + if (($funcs = spl_autoload_functions()) === false) { spl_autoload_register($autoload); } elseif (function_exists('spl_autoload_unregister')) { if (version_compare(PHP_VERSION, '5.3.0', '>=')) { @@ -907,27 +1101,30 @@ public static function registerAutoload() { // places where we need to error out $reflector = new ReflectionMethod($func[0], $func[1]); if (!$reflector->isStatic()) { - throw new Exception(' - HTML Purifier autoloader registrar is not compatible + throw new Exception( + 'HTML Purifier autoloader registrar is not compatible with non-static object methods due to PHP Bug #44144; Please do not use HTMLPurifier.autoload.php (or any file that includes this file); instead, place the code: spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) - after your own autoloaders. - '); + after your own autoloaders.' + ); } // Suprisingly, spl_autoload_register supports the // Class::staticMethod callback format, although call_user_func doesn't - if ($compat) $func = implode('::', $func); + if ($compat) { + $func = implode('::', $func); + } } spl_autoload_unregister($func); } spl_autoload_register($autoload); - foreach ($funcs as $func) spl_autoload_register($func); + foreach ($funcs as $func) { + spl_autoload_register($func); + } } } } - } @@ -943,6 +1140,7 @@ abstract class HTMLPurifier_Definition /** * Has setup() been called yet? + * @type bool */ public $setup = false; @@ -954,31 +1152,35 @@ abstract class HTMLPurifier_Definition * is used and any writes to the raw definition object are short * circuited. See enduser-customize.html for the high-level * picture. + * @type bool */ public $optimized = null; /** * What type of definition is it? + * @type string */ public $type; /** * Sets up the definition object into the final form, something * not done by the constructor - * @param $config HTMLPurifier_Config instance + * @param HTMLPurifier_Config $config */ abstract protected function doSetup($config); /** * Setup function that aborts if already setup - * @param $config HTMLPurifier_Config instance + * @param HTMLPurifier_Config $config */ - public function setup($config) { - if ($this->setup) return; + public function setup($config) + { + if ($this->setup) { + return; + } $this->setup = true; $this->doSetup($config); } - } @@ -996,35 +1198,59 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition /** * Assoc array of attribute name to definition object. + * @type HTMLPurifier_AttrDef[] */ public $info = array(); /** * Constructs the info array. The meat of this class. + * @param HTMLPurifier_Config $config */ - protected function doSetup($config) { - + protected function doSetup($config) + { $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( - array('left', 'right', 'center', 'justify'), false); + array('left', 'right', 'center', 'justify'), + false + ); $border_style = - $this->info['border-bottom-style'] = - $this->info['border-right-style'] = - $this->info['border-left-style'] = - $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double', - 'groove', 'ridge', 'inset', 'outset'), false); + $this->info['border-bottom-style'] = + $this->info['border-right-style'] = + $this->info['border-left-style'] = + $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( + array( + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + 'inset', + 'outset' + ), + false + ); $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'left', 'right', 'both'), false); + array('none', 'left', 'right', 'both'), + false + ); $this->info['float'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'left', 'right'), false); + array('none', 'left', 'right'), + false + ); $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'italic', 'oblique'), false); + array('normal', 'italic', 'oblique'), + false + ); $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'small-caps'), false); + array('normal', 'small-caps'), + false + ); $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( array( @@ -1034,16 +1260,31 @@ protected function doSetup($config) { ); $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( - array('inside', 'outside'), false); + array('inside', 'outside'), + false + ); $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( - array('disc', 'circle', 'square', 'decimal', 'lower-roman', - 'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false); + array( + 'disc', + 'circle', + 'square', + 'decimal', + 'lower-roman', + 'upper-roman', + 'lower-alpha', + 'upper-alpha', + 'none' + ), + false + ); $this->info['list-style-image'] = $uri_or_none; $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( - array('capitalize', 'uppercase', 'lowercase', 'none'), false); + array('capitalize', 'uppercase', 'lowercase', 'none'), + false + ); $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); $this->info['background-image'] = $uri_or_none; @@ -1056,104 +1297,137 @@ protected function doSetup($config) { $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); $border_color = - $this->info['border-top-color'] = - $this->info['border-bottom-color'] = - $this->info['border-left-color'] = - $this->info['border-right-color'] = - $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('transparent')), - new HTMLPurifier_AttrDef_CSS_Color() - )); + $this->info['border-top-color'] = + $this->info['border-bottom-color'] = + $this->info['border-left-color'] = + $this->info['border-right-color'] = + $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('transparent')), + new HTMLPurifier_AttrDef_CSS_Color() + ) + ); $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); $border_width = - $this->info['border-top-width'] = - $this->info['border-bottom-width'] = - $this->info['border-left-width'] = - $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), - new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative - )); + $this->info['border-top-width'] = + $this->info['border-bottom-width'] = + $this->info['border-left-width'] = + $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), + new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative + ) + ); $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); - $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('normal')), - new HTMLPurifier_AttrDef_CSS_Length() - )); - - $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('normal')), - new HTMLPurifier_AttrDef_CSS_Length() - )); - - $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small', - 'small', 'medium', 'large', 'x-large', 'xx-large', - 'larger', 'smaller')), - new HTMLPurifier_AttrDef_CSS_Percentage(), - new HTMLPurifier_AttrDef_CSS_Length() - )); - - $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('normal')), - new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives - new HTMLPurifier_AttrDef_CSS_Length('0'), - new HTMLPurifier_AttrDef_CSS_Percentage(true) - )); + $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'larger', + 'smaller' + ) + ), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); $margin = - $this->info['margin-top'] = - $this->info['margin-bottom'] = - $this->info['margin-left'] = - $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage(), - new HTMLPurifier_AttrDef_Enum(array('auto')) - )); + $this->info['margin-top'] = + $this->info['margin-bottom'] = + $this->info['margin-left'] = + $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); // non-negative $padding = - $this->info['padding-top'] = - $this->info['padding-bottom'] = - $this->info['padding-left'] = - $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length('0'), - new HTMLPurifier_AttrDef_CSS_Percentage(true) - )); + $this->info['padding-top'] = + $this->info['padding-bottom'] = + $this->info['padding-left'] = + $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); - $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage() - )); + $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); - $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length('0'), - new HTMLPurifier_AttrDef_CSS_Percentage(true), - new HTMLPurifier_AttrDef_Enum(array('auto')) - )); + $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); $max = $config->get('CSS.MaxImgLength'); $this->info['width'] = $this->info['height'] = $max === null ? - $trusted_wh : - new HTMLPurifier_AttrDef_Switch('img', - // For img tags: - new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length('0', $max), - new HTMLPurifier_AttrDef_Enum(array('auto')) - )), - // For everyone else: - $trusted_wh - ); + $trusted_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ), + // For everyone else: + $trusted_wh + ); $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); @@ -1161,8 +1435,23 @@ protected function doSetup($config) { // this could use specialized code $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300', - '400', '500', '600', '700', '800', '900'), false); + array( + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900' + ), + false + ); // MUST be called after other font properties, as it references // a CSSDefinition object @@ -1175,27 +1464,44 @@ protected function doSetup($config) { $this->info['border-left'] = $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); - $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array( - 'collapse', 'separate')); + $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( + array('collapse', 'separate') + ); - $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array( - 'top', 'bottom')); + $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( + array('top', 'bottom') + ); - $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array( - 'auto', 'fixed')); + $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( + array('auto', 'fixed') + ); - $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super', - 'top', 'text-top', 'middle', 'bottom', 'text-bottom')), - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage() - )); + $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'baseline', + 'sub', + 'super', + 'top', + 'text-top', + 'middle', + 'bottom', + 'text-bottom' + ) + ), + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); // These CSS properties don't work on many browsers, but we live // in THE FUTURE! - $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')); + $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( + array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') + ); if ($config->get('CSS.Proprietary')) { $this->doSetupProprietary($config); @@ -1218,76 +1524,119 @@ protected function doSetup($config) { $this->setupConfigStuff($config); } - protected function doSetupProprietary($config) { + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupProprietary($config) + { // Internet Explorer only scrollbar colors - $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); // technically not proprietary, but CSS3, and no one supports it - $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); - $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); - $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); // only opacity, for now $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); // more CSS3 $this->info['page-break-after'] = - $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(array('auto','always','avoid','left','right')); - $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto','avoid')); + $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum( + array( + 'auto', + 'always', + 'avoid', + 'left', + 'right' + ) + ); + $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); } - protected function doSetupTricky($config) { - $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array( - 'inline', 'block', 'list-item', 'run-in', 'compact', - 'marker', 'table', 'inline-block', 'inline-table', 'table-row-group', - 'table-header-group', 'table-footer-group', 'table-row', - 'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none' - )); - $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array( - 'visible', 'hidden', 'collapse' - )); + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTricky($config) + { + $this->info['display'] = new HTMLPurifier_AttrDef_Enum( + array( + 'inline', + 'block', + 'list-item', + 'run-in', + 'compact', + 'marker', + 'table', + 'inline-block', + 'inline-table', + 'table-row-group', + 'table-header-group', + 'table-footer-group', + 'table-row', + 'table-column-group', + 'table-column', + 'table-cell', + 'table-caption', + 'none' + ) + ); + $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum( + array('visible', 'hidden', 'collapse') + ); $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); } - protected function doSetupTrusted($config) { - $this->info['position'] = new HTMLPurifier_AttrDef_Enum(array( - 'static', 'relative', 'absolute', 'fixed' - )); + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTrusted($config) + { + $this->info['position'] = new HTMLPurifier_AttrDef_Enum( + array('static', 'relative', 'absolute', 'fixed') + ); $this->info['top'] = $this->info['left'] = $this->info['right'] = - $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage(), - new HTMLPurifier_AttrDef_Enum(array('auto')), - )); - $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Integer(), - new HTMLPurifier_AttrDef_Enum(array('auto')), - )); + $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Integer(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); } /** * Performs extra config-based processing. Based off of * HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_Config $config * @todo Refactor duplicate elements into common class (probably using * composition, not inheritance). */ - protected function setupConfigStuff($config) { - + protected function setupConfigStuff($config) + { // setup allowed elements - $support = "(for information on implementing this, see the ". - "support forums) "; + $support = "(for information on implementing this, see the " . + "support forums) "; $allowed_properties = $config->get('CSS.AllowedProperties'); if ($allowed_properties !== null) { foreach ($this->info as $name => $d) { - if(!isset($allowed_properties[$name])) unset($this->info[$name]); + if (!isset($allowed_properties[$name])) { + unset($this->info[$name]); + } unset($allowed_properties[$name]); } // emit errors @@ -1306,7 +1655,6 @@ protected function setupConfigStuff($config) { } } } - } } @@ -1315,48 +1663,52 @@ protected function setupConfigStuff($config) { /** - * Defines allowed child nodes and validates tokens against it. + * Defines allowed child nodes and validates nodes against it. */ abstract class HTMLPurifier_ChildDef { /** * Type of child definition, usually right-most part of class name lowercase. * Used occasionally in terms of context. + * @type string */ public $type; /** - * Bool that indicates whether or not an empty array of children is okay + * Indicates whether or not an empty array of children is okay. * * This is necessary for redundant checking when changes affecting * a child node may cause a parent node to now be disallowed. + * @type bool */ public $allow_empty; /** - * Lookup array of all elements that this definition could possibly allow + * Lookup array of all elements that this definition could possibly allow. + * @type array */ public $elements = array(); /** * Get lookup of tag names that should not close this element automatically. * All other elements will do so. + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @return array */ - public function getAllowedElements($config) { + public function getAllowedElements($config) + { return $this->elements; } /** * Validates nodes according to definition and returns modification. * - * @param $tokens_of_children Array of HTMLPurifier_Token - * @param $config HTMLPurifier_Config object - * @param $context HTMLPurifier_Context object - * @return bool true to leave nodes as is - * @return bool false to remove parent node - * @return array of replacement child tokens - */ - abstract public function validateChildren($tokens_of_children, $config, $context); + * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @param HTMLPurifier_Context $context HTMLPurifier_Context object + * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children + */ + abstract public function validateChildren($children, $config, $context); } @@ -1382,78 +1734,92 @@ class HTMLPurifier_Config /** * HTML Purifier's version + * @type string */ - public $version = '4.5.0'; + public $version = '4.6.0'; /** - * Bool indicator whether or not to automatically finalize - * the object if a read operation is done + * Whether or not to automatically finalize + * the object if a read operation is done. + * @type bool */ public $autoFinalize = true; // protected member variables /** - * Namespace indexed array of serials for specific namespaces (see - * getSerial() for more info). + * Namespace indexed array of serials for specific namespaces. + * @see getSerial() for more info. + * @type string[] */ protected $serials = array(); /** - * Serial for entire configuration object + * Serial for entire configuration object. + * @type string */ protected $serial; /** - * Parser for variables + * Parser for variables. + * @type HTMLPurifier_VarParser_Flexible */ protected $parser = null; /** - * Reference HTMLPurifier_ConfigSchema for value checking + * Reference HTMLPurifier_ConfigSchema for value checking. + * @type HTMLPurifier_ConfigSchema * @note This is public for introspective purposes. Please don't * abuse! */ public $def; /** - * Indexed array of definitions + * Indexed array of definitions. + * @type HTMLPurifier_Definition[] */ protected $definitions; /** - * Bool indicator whether or not config is finalized + * Whether or not config is finalized. + * @type bool */ protected $finalized = false; /** * Property list containing configuration directives. + * @type array */ protected $plist; /** - * Whether or not a set is taking place due to an - * alias lookup. + * Whether or not a set is taking place due to an alias lookup. + * @type bool */ private $aliasMode; /** - * Set to false if you do not want line and file numbers in errors + * Set to false if you do not want line and file numbers in errors. * (useful when unit testing). This will also compress some errors * and exceptions. + * @type bool */ public $chatty = true; /** * Current lock; only gets to this namespace are allowed. + * @type string */ private $lock; /** - * @param $definition HTMLPurifier_ConfigSchema that defines what directives - * are allowed. + * Constructor + * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines + * what directives are allowed. + * @param HTMLPurifier_PropertyList $parent */ - public function __construct($definition, $parent = null) { + public function __construct($definition, $parent = null) + { $parent = $parent ? $parent : $definition->defaultPlist; $this->plist = new HTMLPurifier_PropertyList($parent); $this->def = $definition; // keep a copy around for checking @@ -1466,10 +1832,11 @@ public function __construct($definition, $parent = null) { * object. Can be: a HTMLPurifier_Config() object, * an array of directives based on loadArray(), * or a string filename of an ini file. - * @param HTMLPurifier_ConfigSchema Schema object - * @return Configured HTMLPurifier_Config object + * @param HTMLPurifier_ConfigSchema $schema Schema object + * @return HTMLPurifier_Config Configured object */ - public static function create($config, $schema = null) { + public static function create($config, $schema = null) + { if ($config instanceof HTMLPurifier_Config) { // pass-through return $config; @@ -1479,57 +1846,79 @@ public static function create($config, $schema = null) { } else { $ret = new HTMLPurifier_Config($schema); } - if (is_string($config)) $ret->loadIni($config); - elseif (is_array($config)) $ret->loadArray($config); + if (is_string($config)) { + $ret->loadIni($config); + } elseif (is_array($config)) $ret->loadArray($config); return $ret; } /** * Creates a new config object that inherits from a previous one. - * @param HTMLPurifier_Config $config Configuration object to inherit - * from. + * @param HTMLPurifier_Config $config Configuration object to inherit from. * @return HTMLPurifier_Config object with $config as its parent. */ - public static function inherit(HTMLPurifier_Config $config) { + public static function inherit(HTMLPurifier_Config $config) + { return new HTMLPurifier_Config($config->def, $config->plist); } /** * Convenience constructor that creates a default configuration object. - * @return Default HTMLPurifier_Config object. + * @return HTMLPurifier_Config default object. */ - public static function createDefault() { + public static function createDefault() + { $definition = HTMLPurifier_ConfigSchema::instance(); $config = new HTMLPurifier_Config($definition); return $config; } /** - * Retreives a value from the configuration. - * @param $key String key + * Retrieves a value from the configuration. + * + * @param string $key String key + * @param mixed $a + * + * @return mixed */ - public function get($key, $a = null) { + public function get($key, $a = null) + { if ($a !== null) { - $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING); + $this->triggerError( + "Using deprecated API: use \$config->get('$key.$a') instead", + E_USER_WARNING + ); $key = "$key.$a"; } - if (!$this->finalized) $this->autoFinalize(); + if (!$this->finalized) { + $this->autoFinalize(); + } if (!isset($this->def->info[$key])) { // can't add % due to SimpleTest bug - $this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key), - E_USER_WARNING); + $this->triggerError( + 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key), + E_USER_WARNING + ); return; } if (isset($this->def->info[$key]->isAlias)) { $d = $this->def->info[$key]; - $this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key, - E_USER_ERROR); + $this->triggerError( + 'Cannot get value from aliased directive, use real name ' . $d->key, + E_USER_ERROR + ); return; } if ($this->lock) { list($ns) = explode('.', $key); if ($ns !== $this->lock) { - $this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR); + $this->triggerError( + 'Cannot get value of namespace ' . $ns . ' when lock for ' . + $this->lock . + ' is active, this probably indicates a Definition setup method ' . + 'is accessing directives that are not within its namespace', + E_USER_ERROR + ); return; } } @@ -1537,15 +1926,24 @@ public function get($key, $a = null) { } /** - * Retreives an array of directives to values from a given namespace - * @param $namespace String namespace + * Retrieves an array of directives to values from a given namespace + * + * @param string $namespace String namespace + * + * @return array */ - public function getBatch($namespace) { - if (!$this->finalized) $this->autoFinalize(); + public function getBatch($namespace) + { + if (!$this->finalized) { + $this->autoFinalize(); + } $full = $this->getAll(); if (!isset($full[$namespace])) { - $this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace), - E_USER_WARNING); + $this->triggerError( + 'Cannot retrieve undefined namespace ' . + htmlspecialchars($namespace), + E_USER_WARNING + ); return; } return $full[$namespace]; @@ -1554,11 +1952,15 @@ public function getBatch($namespace) { /** * Returns a SHA-1 signature of a segment of the configuration object * that uniquely identifies that particular configuration + * + * @param string $namespace Namespace to get serial for + * + * @return string * @note Revision is handled specially and is removed from the batch * before processing! - * @param $namespace Namespace to get serial for */ - public function getBatchSerial($namespace) { + public function getBatchSerial($namespace) + { if (empty($this->serials[$namespace])) { $batch = $this->getBatch($namespace); unset($batch['DefinitionRev']); @@ -1570,8 +1972,11 @@ public function getBatchSerial($namespace) { /** * Returns a SHA-1 signature for the entire configuration object * that uniquely identifies that particular configuration + * + * @return string */ - public function getSerial() { + public function getSerial() + { if (empty($this->serial)) { $this->serial = sha1(serialize($this->getAll())); } @@ -1580,10 +1985,14 @@ public function getSerial() { /** * Retrieves all directives, organized by namespace + * * @warning This is a pretty inefficient function, avoid if you can */ - public function getAll() { - if (!$this->finalized) $this->autoFinalize(); + public function getAll() + { + if (!$this->finalized) { + $this->autoFinalize(); + } $ret = array(); foreach ($this->plist->squash() as $name => $value) { list($ns, $key) = explode('.', $name, 2); @@ -1594,10 +2003,13 @@ public function getAll() { /** * Sets a value to configuration. - * @param $key String key - * @param $value Mixed value + * + * @param string $key key + * @param mixed $value value + * @param mixed $a */ - public function set($key, $value, $a = null) { + public function set($key, $value, $a = null) + { if (strpos($key, '.') === false) { $namespace = $key; $directive = $value; @@ -1607,18 +2019,25 @@ public function set($key, $value, $a = null) { } else { list($namespace) = explode('.', $key); } - if ($this->isFinalized('Cannot set directive after finalization')) return; + if ($this->isFinalized('Cannot set directive after finalization')) { + return; + } if (!isset($this->def->info[$key])) { - $this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', - E_USER_WARNING); + $this->triggerError( + 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', + E_USER_WARNING + ); return; } $def = $this->def->info[$key]; if (isset($def->isAlias)) { if ($this->aliasMode) { - $this->triggerError('Double-aliases not allowed, please fix '. - 'ConfigSchema bug with' . $key, E_USER_ERROR); + $this->triggerError( + 'Double-aliases not allowed, please fix '. + 'ConfigSchema bug with' . $key, + E_USER_ERROR + ); return; } $this->aliasMode = true; @@ -1642,7 +2061,11 @@ public function set($key, $value, $a = null) { try { $value = $this->parser->parse($value, $type, $allow_null); } catch (HTMLPurifier_VarParserException $e) { - $this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING); + $this->triggerError( + 'Value for ' . $key . ' is of invalid type, should be ' . + HTMLPurifier_VarParser::getTypeName($type), + E_USER_WARNING + ); return; } if (is_string($value) && is_object($def)) { @@ -1652,8 +2075,11 @@ public function set($key, $value, $a = null) { } // check to see if the value is allowed if (isset($def->allowed) && !isset($def->allowed[$value])) { - $this->triggerError('Value not supported, valid values are: ' . - $this->_listify($def->allowed), E_USER_WARNING); + $this->triggerError( + 'Value not supported, valid values are: ' . + $this->_listify($def->allowed), + E_USER_WARNING + ); return; } } @@ -1671,63 +2097,83 @@ public function set($key, $value, $a = null) { /** * Convenience function for error reporting + * + * @param array $lookup + * + * @return string */ - private function _listify($lookup) { + private function _listify($lookup) + { $list = array(); - foreach ($lookup as $name => $b) $list[] = $name; + foreach ($lookup as $name => $b) { + $list[] = $name; + } return implode(', ', $list); } /** * Retrieves object reference to the HTML definition. - * @param $raw Return a copy that has not been setup yet. Must be + * + * @param bool $raw Return a copy that has not been setup yet. Must be * called before it's been setup, otherwise won't work. - * @param $optimized If true, this method may return null, to + * @param bool $optimized If true, this method may return null, to * indicate that a cached version of the modified * definition object is available and no further edits * are necessary. Consider using * maybeGetRawHTMLDefinition, which is more explicitly * named, instead. + * + * @return HTMLPurifier_HTMLDefinition */ - public function getHTMLDefinition($raw = false, $optimized = false) { + public function getHTMLDefinition($raw = false, $optimized = false) + { return $this->getDefinition('HTML', $raw, $optimized); } /** * Retrieves object reference to the CSS definition - * @param $raw Return a copy that has not been setup yet. Must be + * + * @param bool $raw Return a copy that has not been setup yet. Must be * called before it's been setup, otherwise won't work. - * @param $optimized If true, this method may return null, to + * @param bool $optimized If true, this method may return null, to * indicate that a cached version of the modified * definition object is available and no further edits * are necessary. Consider using * maybeGetRawCSSDefinition, which is more explicitly * named, instead. + * + * @return HTMLPurifier_CSSDefinition */ - public function getCSSDefinition($raw = false, $optimized = false) { + public function getCSSDefinition($raw = false, $optimized = false) + { return $this->getDefinition('CSS', $raw, $optimized); } /** * Retrieves object reference to the URI definition - * @param $raw Return a copy that has not been setup yet. Must be + * + * @param bool $raw Return a copy that has not been setup yet. Must be * called before it's been setup, otherwise won't work. - * @param $optimized If true, this method may return null, to + * @param bool $optimized If true, this method may return null, to * indicate that a cached version of the modified * definition object is available and no further edits * are necessary. Consider using * maybeGetRawURIDefinition, which is more explicitly * named, instead. + * + * @return HTMLPurifier_URIDefinition */ - public function getURIDefinition($raw = false, $optimized = false) { + public function getURIDefinition($raw = false, $optimized = false) + { return $this->getDefinition('URI', $raw, $optimized); } /** * Retrieves a definition - * @param $type Type of definition: HTML, CSS, etc - * @param $raw Whether or not definition should be returned raw - * @param $optimized Only has an effect when $raw is true. Whether + * + * @param string $type Type of definition: HTML, CSS, etc + * @param bool $raw Whether or not definition should be returned raw + * @param bool $optimized Only has an effect when $raw is true. Whether * or not to return null if the result is already present in * the cache. This is off by default for backwards * compatibility reasons, but you need to do things this @@ -1735,12 +2181,18 @@ public function getURIDefinition($raw = false, $optimized = false) { * Check out enduser-customize.html for more details. * We probably won't ever change this default, as much as the * maybe semantics is the "right thing to do." + * + * @throws HTMLPurifier_Exception + * @return HTMLPurifier_Definition */ - public function getDefinition($type, $raw = false, $optimized = false) { + public function getDefinition($type, $raw = false, $optimized = false) + { if ($optimized && !$raw) { throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); } - if (!$this->finalized) $this->autoFinalize(); + if (!$this->finalized) { + $this->autoFinalize(); + } // temporarily suspend locks, so we can handle recursive definition calls $lock = $this->lock; $this->lock = null; @@ -1758,7 +2210,9 @@ public function getDefinition($type, $raw = false, $optimized = false) { return $def; } else { $def->setup($this); - if ($def->optimized) $cache->add($def, $this); + if ($def->optimized) { + $cache->add($def, $this); + } return $def; } } @@ -1787,23 +2241,36 @@ public function getDefinition($type, $raw = false, $optimized = false) { if ($optimized) { if (is_null($this->get($type . '.DefinitionID'))) { // fatally error out if definition ID not set - throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID"); + throw new HTMLPurifier_Exception( + "Cannot retrieve raw version without specifying %$type.DefinitionID" + ); } } if (!empty($this->definitions[$type])) { $def = $this->definitions[$type]; if ($def->setup && !$optimized) { - $extra = $this->chatty ? " (try moving this code block earlier in your initialization)" : ""; - throw new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup" . $extra); + $extra = $this->chatty ? + " (try moving this code block earlier in your initialization)" : + ""; + throw new HTMLPurifier_Exception( + "Cannot retrieve raw definition after it has already been setup" . + $extra + ); } if ($def->optimized === null) { $extra = $this->chatty ? " (try flushing your cache)" : ""; - throw new HTMLPurifier_Exception("Optimization status of definition is unknown" . $extra); + throw new HTMLPurifier_Exception( + "Optimization status of definition is unknown" . $extra + ); } if ($def->optimized !== $optimized) { $msg = $optimized ? "optimized" : "unoptimized"; - $extra = $this->chatty ? " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" : ""; - throw new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra); + $extra = $this->chatty ? + " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" + : ""; + throw new HTMLPurifier_Exception( + "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra + ); } } // check if definition was in memory @@ -1836,9 +2303,22 @@ public function getDefinition($type, $raw = false, $optimized = false) { if (!$optimized) { if (!is_null($this->get($type . '.DefinitionID'))) { if ($this->chatty) { - $this->triggerError("Due to a documentation error in previous version of HTML Purifier, your definitions are not being cached. If this is OK, you can remove the %$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, modify your code to use maybeGetRawDefinition, and test if the returned value is null before making any edits (if it is null, that means that a cached version is available, and no raw operations are necessary). See Customize for more details", E_USER_WARNING); + $this->triggerError( + 'Due to a documentation error in previous version of HTML Purifier, your ' . + 'definitions are not being cached. If this is OK, you can remove the ' . + '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' . + 'modify your code to use maybeGetRawDefinition, and test if the returned ' . + 'value is null before making any edits (if it is null, that means that a ' . + 'cached version is available, and no raw operations are necessary). See ' . + '' . + 'Customize for more details', + E_USER_WARNING + ); } else { - $this->triggerError("Useless DefinitionID declaration", E_USER_WARNING); + $this->triggerError( + "Useless DefinitionID declaration", + E_USER_WARNING + ); } } } @@ -1850,7 +2330,16 @@ public function getDefinition($type, $raw = false, $optimized = false) { throw new HTMLPurifier_Exception("The impossible happened!"); } - private function initDefinition($type) { + /** + * Initialise definition + * + * @param string $type What type of definition to create + * + * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition + * @throws HTMLPurifier_Exception + */ + private function initDefinition($type) + { // quick checks failed, let's create the object if ($type == 'HTML') { $def = new HTMLPurifier_HTMLDefinition(); @@ -1859,35 +2348,45 @@ private function initDefinition($type) { } elseif ($type == 'URI') { $def = new HTMLPurifier_URIDefinition(); } else { - throw new HTMLPurifier_Exception("Definition of $type type not supported"); + throw new HTMLPurifier_Exception( + "Definition of $type type not supported" + ); } $this->definitions[$type] = $def; return $def; } - public function maybeGetRawDefinition($name) { + public function maybeGetRawDefinition($name) + { return $this->getDefinition($name, true, true); } - public function maybeGetRawHTMLDefinition() { + public function maybeGetRawHTMLDefinition() + { return $this->getDefinition('HTML', true, true); } - public function maybeGetRawCSSDefinition() { + public function maybeGetRawCSSDefinition() + { return $this->getDefinition('CSS', true, true); } - public function maybeGetRawURIDefinition() { + public function maybeGetRawURIDefinition() + { return $this->getDefinition('URI', true, true); } /** * Loads configuration values from an array with the following structure: * Namespace.Directive => Value - * @param $config_array Configuration associative array + * + * @param array $config_array Configuration associative array */ - public function loadArray($config_array) { - if ($this->isFinalized('Cannot load directives after finalization')) return; + public function loadArray($config_array) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } foreach ($config_array as $key => $value) { $key = str_replace('_', '.', $key); if (strpos($key, '.') !== false) { @@ -1895,8 +2394,8 @@ public function loadArray($config_array) { } else { $namespace = $key; $namespace_values = $value; - foreach ($namespace_values as $directive => $value) { - $this->set($namespace .'.'. $directive, $value); + foreach ($namespace_values as $directive => $value2) { + $this->set($namespace .'.'. $directive, $value2); } } } @@ -1906,40 +2405,55 @@ public function loadArray($config_array) { * Returns a list of array(namespace, directive) for all directives * that are allowed in a web-form context as per an allowed * namespaces/directives list. - * @param $allowed List of allowed namespaces/directives + * + * @param array $allowed List of allowed namespaces/directives + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array */ - public static function getAllowedDirectivesForForm($allowed, $schema = null) { + public static function getAllowedDirectivesForForm($allowed, $schema = null) + { if (!$schema) { $schema = HTMLPurifier_ConfigSchema::instance(); } if ($allowed !== true) { - if (is_string($allowed)) $allowed = array($allowed); - $allowed_ns = array(); - $allowed_directives = array(); - $blacklisted_directives = array(); - foreach ($allowed as $ns_or_directive) { - if (strpos($ns_or_directive, '.') !== false) { - // directive - if ($ns_or_directive[0] == '-') { - $blacklisted_directives[substr($ns_or_directive, 1)] = true; - } else { - $allowed_directives[$ns_or_directive] = true; - } - } else { - // namespace - $allowed_ns[$ns_or_directive] = true; - } - } + if (is_string($allowed)) { + $allowed = array($allowed); + } + $allowed_ns = array(); + $allowed_directives = array(); + $blacklisted_directives = array(); + foreach ($allowed as $ns_or_directive) { + if (strpos($ns_or_directive, '.') !== false) { + // directive + if ($ns_or_directive[0] == '-') { + $blacklisted_directives[substr($ns_or_directive, 1)] = true; + } else { + $allowed_directives[$ns_or_directive] = true; + } + } else { + // namespace + $allowed_ns[$ns_or_directive] = true; + } + } } $ret = array(); foreach ($schema->info as $key => $def) { list($ns, $directive) = explode('.', $key, 2); if ($allowed !== true) { - if (isset($blacklisted_directives["$ns.$directive"])) continue; - if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue; + if (isset($blacklisted_directives["$ns.$directive"])) { + continue; + } + if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) { + continue; + } + } + if (isset($def->isAlias)) { + continue; + } + if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { + continue; } - if (isset($def->isAlias)) continue; - if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue; $ret[] = array($ns, $directive); } return $ret; @@ -1948,13 +2462,17 @@ public static function getAllowedDirectivesForForm($allowed, $schema = null) { /** * Loads configuration values from $_GET/$_POST that were posted * via ConfigForm - * @param $array $_GET or $_POST array to import - * @param $index Index/name that the config variables are in - * @param $allowed List of allowed namespaces/directives - * @param $mq_fix Boolean whether or not to enable magic quotes fix - * @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return mixed */ - public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { + public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); $config = HTMLPurifier_Config::create($ret, $schema); return $config; @@ -1962,9 +2480,14 @@ public static function loadArrayFromForm($array, $index = false, $allowed = true /** * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. - * @note Same parameters as loadArrayFromForm + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix */ - public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) { + public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) + { $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); $this->loadArray($ret); } @@ -1972,9 +2495,20 @@ public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_ /** * Prepares an array from a form into something usable for the more * strict parts of HTMLPurifier_Config + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array */ - public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { - if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); + public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + if ($index !== false) { + $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); + } $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); @@ -1986,7 +2520,9 @@ public static function prepareArrayFromForm($array, $index = false, $allowed = t $ret[$ns][$directive] = null; continue; } - if (!isset($array[$skey])) continue; + if (!isset($array[$skey])) { + continue; + } $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; $ret[$ns][$directive] = $value; } @@ -1995,19 +2531,27 @@ public static function prepareArrayFromForm($array, $index = false, $allowed = t /** * Loads configuration values from an ini file - * @param $filename Name of ini file + * + * @param string $filename Name of ini file */ - public function loadIni($filename) { - if ($this->isFinalized('Cannot load directives after finalization')) return; + public function loadIni($filename) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } $array = parse_ini_file($filename, true); $this->loadArray($array); } /** * Checks whether or not the configuration object is finalized. - * @param $error String error message, or false for no error + * + * @param string|bool $error String error message, or false for no error + * + * @return bool */ - public function isFinalized($error = false) { + public function isFinalized($error = false) + { if ($this->finalized && $error) { $this->triggerError($error, E_USER_ERROR); } @@ -2018,7 +2562,8 @@ public function isFinalized($error = false) { * Finalizes configuration only if auto finalize is on and not * already finalized */ - public function autoFinalize() { + public function autoFinalize() + { if ($this->autoFinalize) { $this->finalize(); } else { @@ -2029,7 +2574,8 @@ public function autoFinalize() { /** * Finalizes a configuration object, prohibiting further change */ - public function finalize() { + public function finalize() + { $this->finalized = true; $this->parser = null; } @@ -2037,8 +2583,12 @@ public function finalize() { /** * Produces a nicely formatted error message by supplying the * stack frame information OUTSIDE of HTMLPurifier_Config. + * + * @param string $msg An error message + * @param int $no An error number */ - protected function triggerError($msg, $no) { + protected function triggerError($msg, $no) + { // determine previous stack frame $extra = ''; if ($this->chatty) { @@ -2060,8 +2610,11 @@ protected function triggerError($msg, $no) { /** * Returns a serialized form of the configuration object that can * be reconstituted. + * + * @return string */ - public function serialize() { + public function serialize() + { $this->getDefinition('HTML'); $this->getDefinition('CSS'); $this->getDefinition('URI'); @@ -2077,21 +2630,24 @@ public function serialize() { /** * Configuration definition, defines directives and their defaults. */ -class HTMLPurifier_ConfigSchema { - +class HTMLPurifier_ConfigSchema +{ /** * Defaults of the directives and namespaces. + * @type array * @note This shares the exact same structure as HTMLPurifier_Config::$conf */ public $defaults = array(); /** * The default property list. Do not edit this property list. + * @type array */ public $defaultPlist; /** - * Definition of the directives. The structure of this is: + * Definition of the directives. + * The structure of this is: * * array( * 'Namespace' => array( @@ -2118,22 +2674,27 @@ class HTMLPurifier_ConfigSchema { * This class is friendly with HTMLPurifier_Config. If you need introspection * about the schema, you're better of using the ConfigSchema_Interchange, * which uses more memory but has much richer information. + * @type array */ public $info = array(); /** * Application-wide singleton + * @type HTMLPurifier_ConfigSchema */ - static protected $singleton; + protected static $singleton; - public function __construct() { + public function __construct() + { $this->defaultPlist = new HTMLPurifier_PropertyList(); } /** * Unserializes the default ConfigSchema. + * @return HTMLPurifier_ConfigSchema */ - public static function makeFromSerial() { + public static function makeFromSerial() + { $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); $r = unserialize($contents); if (!$r) { @@ -2145,8 +2706,11 @@ public static function makeFromSerial() { /** * Retrieves an instance of the application-wide configuration definition. + * @param HTMLPurifier_ConfigSchema $prototype + * @return HTMLPurifier_ConfigSchema */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { if ($prototype !== null) { HTMLPurifier_ConfigSchema::$singleton = $prototype; } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { @@ -2160,17 +2724,19 @@ public static function instance($prototype = null) { * @warning Will fail of directive's namespace is defined. * @warning This method's signature is slightly different from the legacy * define() static method! Beware! - * @param $namespace Namespace the directive is in - * @param $name Key of directive - * @param $default Default value of directive - * @param $type Allowed type of the directive. See + * @param string $key Name of directive + * @param mixed $default Default value of directive + * @param string $type Allowed type of the directive. See * HTMLPurifier_DirectiveDef::$type for allowed values - * @param $allow_null Whether or not to allow null values + * @param bool $allow_null Whether or not to allow null values */ - public function add($key, $default, $type, $allow_null) { + public function add($key, $default, $type, $allow_null) + { $obj = new stdclass(); $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; - if ($allow_null) $obj->allow_null = true; + if ($allow_null) { + $obj->allow_null = true; + } $this->info[$key] = $obj; $this->defaults[$key] = $default; $this->defaultPlist->set($key, $default); @@ -2181,11 +2747,11 @@ public function add($key, $default, $type, $allow_null) { * * Directive value aliases are convenient for developers because it lets * them set a directive to several values and get the same result. - * @param $namespace Directive's namespace - * @param $name Name of Directive - * @param $aliases Hash of aliased values to the real alias + * @param string $key Name of Directive + * @param array $aliases Hash of aliased values to the real alias */ - public function addValueAliases($key, $aliases) { + public function addValueAliases($key, $aliases) + { if (!isset($this->info[$key]->aliases)) { $this->info[$key]->aliases = array(); } @@ -2198,22 +2764,21 @@ public function addValueAliases($key, $aliases) { * Defines a set of allowed values for a directive. * @warning This is slightly different from the corresponding static * method definition. - * @param $namespace Namespace of directive - * @param $name Name of directive - * @param $allowed Lookup array of allowed values + * @param string $key Name of directive + * @param array $allowed Lookup array of allowed values */ - public function addAllowedValues($key, $allowed) { + public function addAllowedValues($key, $allowed) + { $this->info[$key]->allowed = $allowed; } /** * Defines a directive alias for backwards compatibility - * @param $namespace - * @param $name Directive that will be aliased - * @param $new_namespace - * @param $new_name Directive that the alias will be to + * @param string $key Directive that will be aliased + * @param string $new_key Directive that the alias will be to */ - public function addAlias($key, $new_key) { + public function addAlias($key, $new_key) + { $obj = new stdclass; $obj->key = $new_key; $obj->isAlias = true; @@ -2223,7 +2788,8 @@ public function addAlias($key, $new_key) { /** * Replaces any stdclass that only has the type property with type integer. */ - public function postProcess() { + public function postProcess() + { foreach ($this->info as $key => $v) { if (count((array) $v) == 1) { $this->info[$key] = $v->type; @@ -2232,7 +2798,6 @@ public function postProcess() { } } } - } @@ -2246,35 +2811,42 @@ class HTMLPurifier_ContentSets { /** - * List of content set strings (pipe seperators) indexed by name. + * List of content set strings (pipe separators) indexed by name. + * @type array */ public $info = array(); /** * List of content set lookups (element => true) indexed by name. + * @type array * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets */ public $lookup = array(); /** - * Synchronized list of defined content sets (keys of info) + * Synchronized list of defined content sets (keys of info). + * @type array */ protected $keys = array(); /** - * Synchronized list of defined content values (values of info) + * Synchronized list of defined content values (values of info). + * @type array */ protected $values = array(); /** * Merges in module's content sets, expands identifiers in the content * sets and populates the keys, values and lookup member variables. - * @param $modules List of HTMLPurifier_HTMLModule + * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule */ - public function __construct($modules) { - if (!is_array($modules)) $modules = array($modules); + public function __construct($modules) + { + if (!is_array($modules)) { + $modules = array($modules); + } // populate content_sets based on module hints // sorry, no way of overloading - foreach ($modules as $module_i => $module) { + foreach ($modules as $module) { foreach ($module->content_sets as $key => $value) { $temp = $this->convertToLookup($value); if (isset($this->lookup[$key])) { @@ -2309,11 +2881,14 @@ public function __construct($modules) { /** * Accepts a definition; generates and assigns a ChildDef for it - * @param $def HTMLPurifier_ElementDef reference - * @param $module Module that defined the ElementDef + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef */ - public function generateChildDef(&$def, $module) { - if (!empty($def->child)) return; // already done! + public function generateChildDef(&$def, $module) + { + if (!empty($def->child)) { // already done! + return; + } $content_model = $def->content_model; if (is_string($content_model)) { // Assume that $this->keys is alphanumeric @@ -2328,7 +2903,8 @@ public function generateChildDef(&$def, $module) { $def->child = $this->getChildDef($def, $module); } - public function generateChildDefCallback($matches) { + public function generateChildDefCallback($matches) + { return $this->info[$matches[0]]; } @@ -2337,10 +2913,12 @@ public function generateChildDefCallback($matches) { * member variables in HTMLPurifier_ElementDef * @note This will also defer to modules for custom HTMLPurifier_ChildDef * subclasses that need content set expansion - * @param $def HTMLPurifier_ElementDef to have ChildDef extracted + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef * @return HTMLPurifier_ChildDef corresponding to ElementDef */ - public function getChildDef($def, $module) { + public function getChildDef($def, $module) + { $value = $def->content_model; if (is_object($value)) { trigger_error( @@ -2365,7 +2943,9 @@ public function getChildDef($def, $module) { if ($module->defines_child_def) { // save a func call $return = $module->getChildDef($def); } - if ($return !== false) return $return; + if ($return !== false) { + return $return; + } // error-out trigger_error( 'Could not determine which ChildDef class to instantiate', @@ -2377,18 +2957,18 @@ public function getChildDef($def, $module) { /** * Converts a string list of elements separated by pipes into * a lookup array. - * @param $string List of elements - * @return Lookup array of elements + * @param string $string List of elements + * @return array Lookup array of elements */ - protected function convertToLookup($string) { + protected function convertToLookup($string) + { $array = explode('|', str_replace(' ', '', $string)); $ret = array(); - foreach ($array as $i => $k) { + foreach ($array as $k) { $ret[$k] = true; } return $ret; } - } @@ -2407,18 +2987,22 @@ class HTMLPurifier_Context /** * Private array that stores the references. + * @type array */ private $_storage = array(); /** * Registers a variable into the context. - * @param $name String name - * @param $ref Reference to variable to be registered + * @param string $name String name + * @param mixed $ref Reference to variable to be registered */ - public function register($name, &$ref) { - if (isset($this->_storage[$name])) { - trigger_error("Name $name produces collision, cannot re-register", - E_USER_ERROR); + public function register($name, &$ref) + { + if (array_key_exists($name, $this->_storage)) { + trigger_error( + "Name $name produces collision, cannot re-register", + E_USER_ERROR + ); return; } $this->_storage[$name] =& $ref; @@ -2426,14 +3010,18 @@ public function register($name, &$ref) { /** * Retrieves a variable reference from the context. - * @param $name String name - * @param $ignore_error Boolean whether or not to ignore error + * @param string $name String name + * @param bool $ignore_error Boolean whether or not to ignore error + * @return mixed */ - public function &get($name, $ignore_error = false) { - if (!isset($this->_storage[$name])) { + public function &get($name, $ignore_error = false) + { + if (!array_key_exists($name, $this->_storage)) { if (!$ignore_error) { - trigger_error("Attempted to retrieve non-existent variable $name", - E_USER_ERROR); + trigger_error( + "Attempted to retrieve non-existent variable $name", + E_USER_ERROR + ); } $var = null; // so we can return by reference return $var; @@ -2442,13 +3030,16 @@ public function &get($name, $ignore_error = false) { } /** - * Destorys a variable in the context. - * @param $name String name + * Destroys a variable in the context. + * @param string $name String name */ - public function destroy($name) { - if (!isset($this->_storage[$name])) { - trigger_error("Attempted to destroy non-existent variable $name", - E_USER_ERROR); + public function destroy($name) + { + if (!array_key_exists($name, $this->_storage)) { + trigger_error( + "Attempted to destroy non-existent variable $name", + E_USER_ERROR + ); return; } unset($this->_storage[$name]); @@ -2456,22 +3047,24 @@ public function destroy($name) { /** * Checks whether or not the variable exists. - * @param $name String name + * @param string $name String name + * @return bool */ - public function exists($name) { - return isset($this->_storage[$name]); + public function exists($name) + { + return array_key_exists($name, $this->_storage); } /** * Loads a series of variables from an associative array - * @param $context_array Assoc array of variables to load + * @param array $context_array Assoc array of variables to load */ - public function loadArray($context_array) { + public function loadArray($context_array) + { foreach ($context_array as $key => $discard) { $this->register($key, $context_array[$key]); } } - } @@ -2488,22 +3081,27 @@ public function loadArray($context_array) { */ abstract class HTMLPurifier_DefinitionCache { - + /** + * @type string + */ public $type; /** - * @param $name Type of definition objects this instance of the + * @param string $type Type of definition objects this instance of the * cache will handle. */ - public function __construct($type) { + public function __construct($type) + { $this->type = $type; } /** * Generates a unique identifier for a particular configuration - * @param Instance of HTMLPurifier_Config + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @return string */ - public function generateKey($config) { + public function generateKey($config) + { return $config->version . ',' . // possibly replace with function calls $config->getBatchSerial($this->type) . ',' . $config->get($this->type . '.DefinitionRev'); @@ -2512,30 +3110,37 @@ public function generateKey($config) { /** * Tests whether or not a key is old with respect to the configuration's * version and revision number. - * @param $key Key to test - * @param $config Instance of HTMLPurifier_Config to test against + * @param string $key Key to test + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against + * @return bool */ - public function isOld($key, $config) { - if (substr_count($key, ',') < 2) return true; + public function isOld($key, $config) + { + if (substr_count($key, ',') < 2) { + return true; + } list($version, $hash, $revision) = explode(',', $key, 3); $compare = version_compare($version, $config->version); // version mismatch, is always old - if ($compare != 0) return true; + if ($compare != 0) { + return true; + } // versions match, ids match, check revision number - if ( - $hash == $config->getBatchSerial($this->type) && - $revision < $config->get($this->type . '.DefinitionRev') - ) return true; + if ($hash == $config->getBatchSerial($this->type) && + $revision < $config->get($this->type . '.DefinitionRev')) { + return true; + } return false; } /** * Checks if a definition's type jives with the cache's type * @note Throws an error on failure - * @param $def Definition object to check - * @return Boolean true if good, false if not + * @param HTMLPurifier_Definition $def Definition object to check + * @return bool true if good, false if not */ - public function checkDefType($def) { + public function checkDefType($def) + { if ($def->type !== $this->type) { trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}"); return false; @@ -2545,31 +3150,40 @@ public function checkDefType($def) { /** * Adds a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config */ abstract public function add($def, $config); /** * Unconditionally saves a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config */ abstract public function set($def, $config); /** * Replace an object in the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config */ abstract public function replace($def, $config); /** * Retrieves a definition object from the cache + * @param HTMLPurifier_Config $config */ abstract public function get($config); /** * Removes a definition object to the cache + * @param HTMLPurifier_Config $config */ abstract public function remove($config); /** * Clears all objects from cache + * @param HTMLPurifier_Config $config */ abstract public function flush($config); @@ -2578,9 +3192,9 @@ abstract public function flush($config); * @note Be carefuly implementing this method as flush. Flush must * not interfere with other Definition types, and cleanup() * should not be repeatedly called by userland code. + * @param HTMLPurifier_Config $config */ abstract public function cleanup($config); - } @@ -2592,22 +3206,36 @@ abstract public function cleanup($config); */ class HTMLPurifier_DefinitionCacheFactory { - + /** + * @type array + */ protected $caches = array('Serializer' => array()); + + /** + * @type array + */ protected $implementations = array(); + + /** + * @type HTMLPurifier_DefinitionCache_Decorator[] + */ protected $decorators = array(); /** * Initialize default decorators */ - public function setup() { + public function setup() + { $this->addDecorator('Cleanup'); } /** * Retrieves an instance of global definition cache factory. + * @param HTMLPurifier_DefinitionCacheFactory $prototype + * @return HTMLPurifier_DefinitionCacheFactory */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { static $instance; if ($prototype !== null) { $instance = $prototype; @@ -2620,19 +3248,22 @@ public static function instance($prototype = null) { /** * Registers a new definition cache object - * @param $short Short name of cache object, for reference - * @param $long Full class name of cache object, for construction + * @param string $short Short name of cache object, for reference + * @param string $long Full class name of cache object, for construction */ - public function register($short, $long) { + public function register($short, $long) + { $this->implementations[$short] = $long; } /** * Factory method that creates a cache object based on configuration - * @param $name Name of definitions handled by cache - * @param $config Instance of HTMLPurifier_Config + * @param string $type Name of definitions handled by cache + * @param HTMLPurifier_Config $config Config instance + * @return mixed */ - public function create($type, $config) { + public function create($type, $config) + { $method = $config->get('Cache.DefinitionImpl'); if ($method === null) { return new HTMLPurifier_DefinitionCache_Null($type); @@ -2640,10 +3271,8 @@ public function create($type, $config) { if (!empty($this->caches[$method][$type])) { return $this->caches[$method][$type]; } - if ( - isset($this->implementations[$method]) && - class_exists($class = $this->implementations[$method], false) - ) { + if (isset($this->implementations[$method]) && + class_exists($class = $this->implementations[$method], false)) { $cache = new $class($type); } else { if ($method != 'Serializer') { @@ -2663,16 +3292,16 @@ class_exists($class = $this->implementations[$method], false) /** * Registers a decorator to add to all new cache objects - * @param + * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator */ - public function addDecorator($decorator) { + public function addDecorator($decorator) + { if (is_string($decorator)) { $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; $decorator = new $class; } $this->decorators[$decorator->name] = $decorator; } - } @@ -2689,42 +3318,55 @@ class HTMLPurifier_Doctype { /** * Full name of doctype + * @type string */ public $name; /** * List of standard modules (string identifiers or literal objects) * that this doctype uses + * @type array */ public $modules = array(); /** * List of modules to use for tidying up code + * @type array */ public $tidyModules = array(); /** * Is the language derived from XML (i.e. XHTML)? + * @type bool */ public $xml = true; /** * List of aliases for this doctype + * @type array */ public $aliases = array(); /** * Public DTD identifier + * @type string */ public $dtdPublic; /** * System DTD identifier + * @type string */ public $dtdSystem; - public function __construct($name = null, $xml = true, $modules = array(), - $tidyModules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null + public function __construct( + $name = null, + $xml = true, + $modules = array(), + $tidyModules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null ) { $this->name = $name; $this->xml = $xml; @@ -2744,12 +3386,14 @@ class HTMLPurifier_DoctypeRegistry { /** - * Hash of doctype names to doctype objects + * Hash of doctype names to doctype objects. + * @type array */ protected $doctypes; /** - * Lookup table of aliases to real doctype names + * Lookup table of aliases to real doctype names. + * @type array */ protected $aliases; @@ -2757,32 +3401,57 @@ class HTMLPurifier_DoctypeRegistry * Registers a doctype to the registry * @note Accepts a fully-formed doctype object, or the * parameters for constructing a doctype object - * @param $doctype Name of doctype or literal doctype object - * @param $modules Modules doctype will load - * @param $modules_for_modes Modules doctype will load for certain modes - * @param $aliases Alias names for doctype - * @return Editable registered doctype - */ - public function register($doctype, $xml = true, $modules = array(), - $tidy_modules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null + * @param string $doctype Name of doctype or literal doctype object + * @param bool $xml + * @param array $modules Modules doctype will load + * @param array $tidy_modules Modules doctype will load for certain modes + * @param array $aliases Alias names for doctype + * @param string $dtd_public + * @param string $dtd_system + * @return HTMLPurifier_Doctype Editable registered doctype + */ + public function register( + $doctype, + $xml = true, + $modules = array(), + $tidy_modules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null ) { - if (!is_array($modules)) $modules = array($modules); - if (!is_array($tidy_modules)) $tidy_modules = array($tidy_modules); - if (!is_array($aliases)) $aliases = array($aliases); + if (!is_array($modules)) { + $modules = array($modules); + } + if (!is_array($tidy_modules)) { + $tidy_modules = array($tidy_modules); + } + if (!is_array($aliases)) { + $aliases = array($aliases); + } if (!is_object($doctype)) { $doctype = new HTMLPurifier_Doctype( - $doctype, $xml, $modules, $tidy_modules, $aliases, $dtd_public, $dtd_system + $doctype, + $xml, + $modules, + $tidy_modules, + $aliases, + $dtd_public, + $dtd_system ); } $this->doctypes[$doctype->name] = $doctype; $name = $doctype->name; // hookup aliases foreach ($doctype->aliases as $alias) { - if (isset($this->doctypes[$alias])) continue; + if (isset($this->doctypes[$alias])) { + continue; + } $this->aliases[$alias] = $name; } // remove old aliases - if (isset($this->aliases[$name])) unset($this->aliases[$name]); + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } return $doctype; } @@ -2790,11 +3459,14 @@ public function register($doctype, $xml = true, $modules = array(), * Retrieves reference to a doctype of a certain name * @note This function resolves aliases * @note When possible, use the more fully-featured make() - * @param $doctype Name of doctype - * @return Editable doctype object + * @param string $doctype Name of doctype + * @return HTMLPurifier_Doctype Editable doctype object */ - public function get($doctype) { - if (isset($this->aliases[$doctype])) $doctype = $this->aliases[$doctype]; + public function get($doctype) + { + if (isset($this->aliases[$doctype])) { + $doctype = $this->aliases[$doctype]; + } if (!isset($this->doctypes[$doctype])) { trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); $anon = new HTMLPurifier_Doctype($doctype); @@ -2810,20 +3482,30 @@ public function get($doctype) { * can hold on to (this is necessary in order to tell * Generator whether or not the current document is XML * based or not). + * @param HTMLPurifier_Config $config + * @return HTMLPurifier_Doctype */ - public function make($config) { + public function make($config) + { return clone $this->get($this->getDoctypeFromConfig($config)); } /** * Retrieves the doctype from the configuration object + * @param HTMLPurifier_Config $config + * @return string */ - public function getDoctypeFromConfig($config) { + public function getDoctypeFromConfig($config) + { // recommended test $doctype = $config->get('HTML.Doctype'); - if (!empty($doctype)) return $doctype; + if (!empty($doctype)) { + return $doctype; + } $doctype = $config->get('HTML.CustomDoctype'); - if (!empty($doctype)) return $doctype; + if (!empty($doctype)) { + return $doctype; + } // backwards-compatibility if ($config->get('HTML.XHTML')) { $doctype = 'XHTML 1.0'; @@ -2837,7 +3519,6 @@ public function getDoctypeFromConfig($config) { } return $doctype; } - } @@ -2854,15 +3535,16 @@ public function getDoctypeFromConfig($config) { */ class HTMLPurifier_ElementDef { - /** * Does the definition work by itself, or is it created solely * for the purpose of merging into another definition? + * @type bool */ public $standalone = true; /** - * Associative array of attribute name to HTMLPurifier_AttrDef + * Associative array of attribute name to HTMLPurifier_AttrDef. + * @type array * @note Before being processed by HTMLPurifier_AttrCollections * when modules are finalized during * HTMLPurifier_HTMLDefinition->setup(), this array may also @@ -2887,26 +3569,30 @@ class HTMLPurifier_ElementDef // nuking. /** - * List of tags HTMLPurifier_AttrTransform to be done before validation + * List of tags HTMLPurifier_AttrTransform to be done before validation. + * @type array */ public $attr_transform_pre = array(); /** - * List of tags HTMLPurifier_AttrTransform to be done after validation + * List of tags HTMLPurifier_AttrTransform to be done after validation. + * @type array */ public $attr_transform_post = array(); /** * HTMLPurifier_ChildDef of this tag. + * @type HTMLPurifier_ChildDef */ public $child; /** - * Abstract string representation of internal ChildDef rules. See - * HTMLPurifier_ContentSets for how this is parsed and then transformed + * Abstract string representation of internal ChildDef rules. + * @see HTMLPurifier_ContentSets for how this is parsed and then transformed * into an HTMLPurifier_ChildDef. * @warning This is a temporary variable that is not available after * being processed by HTMLDefinition + * @type string */ public $content_model; @@ -2916,27 +3602,29 @@ class HTMLPurifier_ElementDef * @warning This must be lowercase * @warning This is a temporary variable that is not available after * being processed by HTMLDefinition + * @type string */ public $content_model_type; - - /** * Does the element have a content model (#PCDATA | Inline)*? This * is important for chameleon ins and del processing in * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't * have to worry about this one. + * @type bool */ public $descendants_are_inline = false; /** - * List of the names of required attributes this element has. Dynamically - * populated by HTMLPurifier_HTMLDefinition::getElement + * List of the names of required attributes this element has. + * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement() + * @type array */ public $required_attr = array(); /** * Lookup table of tags excluded from all descendants of this tag. + * @type array * @note SGML permits exclusions for all descendants, but this is * not possible with DTDs or XML Schemas. W3C has elected to * use complicated compositions of content_models to simulate @@ -2950,6 +3638,7 @@ class HTMLPurifier_ElementDef /** * This tag is explicitly auto-closed by the following tags. + * @type array */ public $autoclose = array(); @@ -2957,19 +3646,22 @@ class HTMLPurifier_ElementDef * If a foreign element is found in this element, test if it is * allowed by this sub-element; if it is, instead of closing the * current element, place it inside this element. + * @type string */ public $wrap; /** * Whether or not this is a formatting element affected by the * "Active Formatting Elements" algorithm. + * @type bool */ public $formatting; /** * Low-level factory constructor for creating new standalone element defs */ - public static function create($content_model, $content_model_type, $attr) { + public static function create($content_model, $content_model_type, $attr) + { $def = new HTMLPurifier_ElementDef(); $def->content_model = $content_model; $def->content_model_type = $content_model_type; @@ -2981,11 +3673,12 @@ public static function create($content_model, $content_model_type, $attr) { * Merges the values of another element definition into this one. * Values from the new element def take precedence if a value is * not mergeable. + * @param HTMLPurifier_ElementDef $def */ - public function mergeIn($def) { - + public function mergeIn($def) + { // later keys takes precedence - foreach($def->attr as $k => $v) { + foreach ($def->attr as $k => $v) { if ($k === 0) { // merge in the includes // sorry, no way to override an include @@ -2995,7 +3688,9 @@ public function mergeIn($def) { continue; } if ($v === false) { - if (isset($this->attr[$k])) unset($this->attr[$k]); + if (isset($this->attr[$k])) { + unset($this->attr[$k]); + } continue; } $this->attr[$k] = $v; @@ -3004,19 +3699,24 @@ public function mergeIn($def) { $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre); $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post); - if(!empty($def->content_model)) { + if (!empty($def->content_model)) { $this->content_model = str_replace("#SUPER", $this->content_model, $def->content_model); $this->child = false; } - if(!empty($def->content_model_type)) { + if (!empty($def->content_model_type)) { $this->content_model_type = $def->content_model_type; $this->child = false; } - if(!is_null($def->child)) $this->child = $def->child; - if(!is_null($def->formatting)) $this->formatting = $def->formatting; - if($def->descendants_are_inline) $this->descendants_are_inline = $def->descendants_are_inline; - + if (!is_null($def->child)) { + $this->child = $def->child; + } + if (!is_null($def->formatting)) { + $this->formatting = $def->formatting; + } + if ($def->descendants_are_inline) { + $this->descendants_are_inline = $def->descendants_are_inline; + } } /** @@ -3024,16 +3724,18 @@ public function mergeIn($def) { * @param $a1 Array by reference that is merged into * @param $a2 Array that merges into $a1 */ - private function _mergeAssocArray(&$a1, $a2) { + private function _mergeAssocArray(&$a1, $a2) + { foreach ($a2 as $k => $v) { if ($v === false) { - if (isset($a1[$k])) unset($a1[$k]); + if (isset($a1[$k])) { + unset($a1[$k]); + } continue; } $a1[$k] = $v; } } - } @@ -3050,19 +3752,27 @@ class HTMLPurifier_Encoder /** * Constructor throws fatal error if you attempt to instantiate class */ - private function __construct() { + private function __construct() + { trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR); } /** * Error-handler that mutes errors, alternative to shut-up operator. */ - public static function muteErrorHandler() {} + public static function muteErrorHandler() + { + } /** * iconv wrapper which mutes errors, but doesn't work around bugs. + * @param string $in Input encoding + * @param string $out Output encoding + * @param string $text The text to convert + * @return string */ - public static function unsafeIconv($in, $out, $text) { + public static function unsafeIconv($in, $out, $text) + { set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); $r = iconv($in, $out, $text); restore_error_handler(); @@ -3071,8 +3781,14 @@ public static function unsafeIconv($in, $out, $text) { /** * iconv wrapper which mutes errors and works around bugs. - */ - public static function iconv($in, $out, $text, $max_chunk_size = 8000) { + * @param string $in Input encoding + * @param string $out Output encoding + * @param string $text The text to convert + * @param int $max_chunk_size + * @return string + */ + public static function iconv($in, $out, $text, $max_chunk_size = 8000) + { $code = self::testIconvTruncateBug(); if ($code == self::ICONV_OK) { return self::unsafeIconv($in, $out, $text); @@ -3127,6 +3843,10 @@ public static function iconv($in, $out, $text, $max_chunk_size = 8000) { * It will parse according to UTF-8 and return a valid UTF8 string, with * non-SGML codepoints excluded. * + * @param string $str The string to clean + * @param bool $force_php + * @return string + * * @note Just for reference, the non-SGML code points are 0 to 31 and * 127 to 159, inclusive. However, we allow code points 9, 10 * and 13, which are the tab, line feed and carriage return @@ -3146,14 +3866,17 @@ public static function iconv($in, $out, $text, $max_chunk_size = 8000) { * would need that, and I'm probably not going to implement them. * Once again, PHP 6 should solve all our problems. */ - public static function cleanUTF8($str, $force_php = false) { - + public static function cleanUTF8($str, $force_php = false) + { // UTF-8 validity is checked since PHP 4.3.5 // This is an optimization: if the string is already valid UTF-8, no // need to do PHP stuff. 99% of the time, this will be the case. // The regexp matches the XML char production, as well as well as excluding // non-SGML codepoints U+007F to U+009F - if (preg_match('/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', $str)) { + if (preg_match( + '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', + $str + )) { return $str; } @@ -3172,7 +3895,7 @@ public static function cleanUTF8($str, $force_php = false) { $char = ''; $len = strlen($str); - for($i = 0; $i < $len; $i++) { + for ($i = 0; $i < $len; $i++) { $in = ord($str{$i}); $char .= $str[$i]; // append byte to char if (0 == $mState) { @@ -3325,8 +4048,9 @@ public static function cleanUTF8($str, $force_php = false) { // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes // +----------+----------+----------+----------+ - public static function unichr($code) { - if($code > 1114111 or $code < 0 or + public static function unichr($code) + { + if ($code > 1114111 or $code < 0 or ($code >= 55296 and $code <= 57343) ) { // bits are set outside the "valid" range as defined // by UNICODE 4.1.0 @@ -3344,7 +4068,7 @@ public static function unichr($code) { $y = (($code & 2047) >> 6) | 192; } else { $y = (($code & 4032) >> 6) | 128; - if($code < 65536) { + if ($code < 65536) { $z = (($code >> 12) & 15) | 224; } else { $z = (($code >> 12) & 63) | 128; @@ -3354,15 +4078,25 @@ public static function unichr($code) { } // set up the actual character $ret = ''; - if($w) $ret .= chr($w); - if($z) $ret .= chr($z); - if($y) $ret .= chr($y); + if ($w) { + $ret .= chr($w); + } + if ($z) { + $ret .= chr($z); + } + if ($y) { + $ret .= chr($y); + } $ret .= chr($x); return $ret; } - public static function iconvAvailable() { + /** + * @return bool + */ + public static function iconvAvailable() + { static $iconv = null; if ($iconv === null) { $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; @@ -3371,13 +4105,22 @@ public static function iconvAvailable() { } /** - * Converts a string to UTF-8 based on configuration. + * Convert a string to UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string */ - public static function convertToUTF8($str, $config, $context) { + public static function convertToUTF8($str, $config, $context) + { $encoding = $config->get('Core.Encoding'); - if ($encoding === 'utf-8') return $str; + if ($encoding === 'utf-8') { + return $str; + } static $iconv = null; - if ($iconv === null) $iconv = self::iconvAvailable(); + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } if ($iconv && !$config->get('Test.ForceNoIconv')) { // unaffected by bugs, since UTF-8 support all characters $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str); @@ -3399,29 +4142,44 @@ public static function convertToUTF8($str, $config, $context) { if ($bug == self::ICONV_OK) { trigger_error('Encoding not supported, please install iconv', E_USER_ERROR); } else { - trigger_error('You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', E_USER_ERROR); + trigger_error( + 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' . + 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', + E_USER_ERROR + ); } } /** * Converts a string from UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string * @note Currently, this is a lossy conversion, with unexpressable * characters being omitted. */ - public static function convertFromUTF8($str, $config, $context) { + public static function convertFromUTF8($str, $config, $context) + { $encoding = $config->get('Core.Encoding'); if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { $str = self::convertToASCIIDumbLossless($str); } - if ($encoding === 'utf-8') return $str; + if ($encoding === 'utf-8') { + return $str; + } static $iconv = null; - if ($iconv === null) $iconv = self::iconvAvailable(); + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } if ($iconv && !$config->get('Test.ForceNoIconv')) { // Undo our previous fix in convertToUTF8, otherwise iconv will barf $ascii_fix = self::testEncodingSupportsASCII($encoding); if (!$escape && !empty($ascii_fix)) { $clear_fix = array(); - foreach ($ascii_fix as $utf8 => $native) $clear_fix[$utf8] = ''; + foreach ($ascii_fix as $utf8 => $native) { + $clear_fix[$utf8] = ''; + } $str = strtr($str, $clear_fix); } $str = strtr($str, array_flip($ascii_fix)); @@ -3441,8 +4199,8 @@ public static function convertFromUTF8($str, $config, $context) { /** * Lossless (character-wise) conversion of HTML to ASCII - * @param $str UTF-8 string to be converted to ASCII - * @returns ASCII encoded string with non-ASCII character entity-ized + * @param string $str UTF-8 string to be converted to ASCII + * @return string ASCII encoded string with non-ASCII character entity-ized * @warning Adapted from MediaWiki, claiming fair use: this is a common * algorithm. If you disagree with this license fudgery, * implement it yourself. @@ -3455,27 +4213,28 @@ public static function convertFromUTF8($str, $config, $context) { * @note Sort of with cleanUTF8() but it assumes that $str is * well-formed UTF-8 */ - public static function convertToASCIIDumbLossless($str) { + public static function convertToASCIIDumbLossless($str) + { $bytesleft = 0; $result = ''; $working = 0; $len = strlen($str); - for( $i = 0; $i < $len; $i++ ) { - $bytevalue = ord( $str[$i] ); - if( $bytevalue <= 0x7F ) { //0xxx xxxx - $result .= chr( $bytevalue ); + for ($i = 0; $i < $len; $i++) { + $bytevalue = ord($str[$i]); + if ($bytevalue <= 0x7F) { //0xxx xxxx + $result .= chr($bytevalue); $bytesleft = 0; - } elseif( $bytevalue <= 0xBF ) { //10xx xxxx + } elseif ($bytevalue <= 0xBF) { //10xx xxxx $working = $working << 6; $working += ($bytevalue & 0x3F); $bytesleft--; - if( $bytesleft <= 0 ) { + if ($bytesleft <= 0) { $result .= "&#" . $working . ";"; } - } elseif( $bytevalue <= 0xDF ) { //110x xxxx + } elseif ($bytevalue <= 0xDF) { //110x xxxx $working = $bytevalue & 0x1F; $bytesleft = 1; - } elseif( $bytevalue <= 0xEF ) { //1110 xxxx + } elseif ($bytevalue <= 0xEF) { //1110 xxxx $working = $bytevalue & 0x0F; $bytesleft = 2; } else { //1111 0xxx @@ -3509,9 +4268,10 @@ public static function convertToASCIIDumbLossless($str) { * characters, as long as PHP ignores the error code. If PHP starts * paying attention to the error code, iconv becomes unusable. * - * @returns Error code indicating severity of bug. + * @return int Error code indicating severity of bug. */ - public static function testIconvTruncateBug() { + public static function testIconvTruncateBug() + { static $code = null; if ($code === null) { // better not use iconv, otherwise infinite loop! @@ -3521,7 +4281,11 @@ public static function testIconvTruncateBug() { } elseif (($c = strlen($r)) < 9000) { $code = self::ICONV_TRUNCATES; } elseif ($c > 9000) { - trigger_error('Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: include your iconv version as per phpversion()', E_USER_ERROR); + trigger_error( + 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' . + 'include your iconv version as per phpversion()', + E_USER_ERROR + ); } else { $code = self::ICONV_OK; } @@ -3540,7 +4304,8 @@ public static function testIconvTruncateBug() { * @return Array of UTF-8 characters to their corresponding ASCII, * which can be used to "undo" any overzealous iconv action. */ - public static function testEncodingSupportsASCII($encoding, $bypass = false) { + public static function testEncodingSupportsASCII($encoding, $bypass = false) + { // All calls to iconv here are unsafe, proof by case analysis: // If ICONV_OK, no difference. // If ICONV_TRUNCATE, all calls involve one character inputs, @@ -3548,7 +4313,9 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) { // If ICONV_UNUSABLE, this call is irrelevant static $encodings = array(); if (!$bypass) { - if (isset($encodings[$encoding])) return $encodings[$encoding]; + if (isset($encodings[$encoding])) { + return $encodings[$encoding]; + } $lenc = strtolower($encoding); switch ($lenc) { case 'shift_jis': @@ -3556,15 +4323,18 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) { case 'johab': return array("\xE2\x82\xA9" => '\\'); } - if (strpos($lenc, 'iso-8859-') === 0) return array(); + if (strpos($lenc, 'iso-8859-') === 0) { + return array(); + } } $ret = array(); - if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) return false; + if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) { + return false; + } for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars $c = chr($i); // UTF-8 char $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion - if ( - $r === '' || + if ($r === '' || // This line is needed for iconv implementations that do not // omit characters that do not exist in the target character set ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c) @@ -3578,8 +4348,6 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) { $encodings[$encoding] = $ret; return $ret; } - - } @@ -3589,20 +4357,23 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) { /** * Object that provides entity lookup table from entity name to character */ -class HTMLPurifier_EntityLookup { - +class HTMLPurifier_EntityLookup +{ /** * Assoc array of entity name to character represented. + * @type array */ public $table; /** * Sets up the entity lookup table from the serialized file contents. + * @param bool $file * @note The serialized contents are versioned, but were generated * using the maintenance script generate_entity_file.php * @warning This is not in constructor to help enforce the Singleton */ - public function setup($file = false) { + public function setup($file = false) + { if (!$file) { $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser'; } @@ -3611,9 +4382,11 @@ public function setup($file = false) { /** * Retrieves sole instance of the object. - * @param Optional prototype of custom lookup table to overload with. + * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. + * @return HTMLPurifier_EntityLookup */ - public static function instance($prototype = false) { + public static function instance($prototype = false) + { // no references, since PHP doesn't copy unless modified static $instance = null; if ($prototype) { @@ -3624,7 +4397,6 @@ public static function instance($prototype = false) { } return $instance; } - } @@ -3643,19 +4415,21 @@ class HTMLPurifier_EntityParser /** * Reference to entity lookup table. + * @type HTMLPurifier_EntityLookup */ protected $_entity_lookup; /** * Callback regex string for parsing entities. + * @type string */ protected $_substituteEntitiesRegex = -'/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/'; -// 1. hex 2. dec 3. string (XML style) - + '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/'; + // 1. hex 2. dec 3. string (XML style) /** * Decimal to parsed string conversion table for special entities. + * @type array */ protected $_special_dec2str = array( @@ -3668,6 +4442,7 @@ class HTMLPurifier_EntityParser /** * Stripped entity names to decimal conversion table for special entities. + * @type array */ protected $_special_ent2dec = array( @@ -3682,41 +4457,45 @@ class HTMLPurifier_EntityParser * running this whenever you have parsed character is t3h 5uck, we run * it before everything else. * - * @param $string String to have non-special entities parsed. - * @returns Parsed string. + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. */ - public function substituteNonSpecialEntities($string) { + public function substituteNonSpecialEntities($string) + { // it will try to detect missing semicolons, but don't rely on it return preg_replace_callback( $this->_substituteEntitiesRegex, array($this, 'nonSpecialEntityCallback'), $string - ); + ); } /** * Callback function for substituteNonSpecialEntities() that does the work. * - * @param $matches PCRE matches array, with 0 the entire match, and + * @param array $matches PCRE matches array, with 0 the entire match, and * either index 1, 2 or 3 set with a hex value, dec value, * or string (respectively). - * @returns Replacement string. + * @return string Replacement string. */ - protected function nonSpecialEntityCallback($matches) { + protected function nonSpecialEntityCallback($matches) + { // replaces all but big five $entity = $matches[0]; $is_num = (@$matches[0][1] === '#'); if ($is_num) { $is_hex = (@$entity[2] === 'x'); $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; - // abort for special characters - if (isset($this->_special_dec2str[$code])) return $entity; - + if (isset($this->_special_dec2str[$code])) { + return $entity; + } return HTMLPurifier_Encoder::unichr($code); } else { - if (isset($this->_special_ent2dec[$matches[3]])) return $entity; + if (isset($this->_special_ent2dec[$matches[3]])) { + return $entity; + } if (!$this->_entity_lookup) { $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); } @@ -3734,14 +4513,16 @@ protected function nonSpecialEntityCallback($matches) { * @notice We try to avoid calling this function because otherwise, it * would have to be called a lot (for every parsed section). * - * @param $string String to have non-special entities parsed. - * @returns Parsed string. + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. */ - public function substituteSpecialEntities($string) { + public function substituteSpecialEntities($string) + { return preg_replace_callback( $this->_substituteEntitiesRegex, array($this, 'specialEntityCallback'), - $string); + $string + ); } /** @@ -3749,12 +4530,13 @@ public function substituteSpecialEntities($string) { * * This callback has same syntax as nonSpecialEntityCallback(). * - * @param $matches PCRE-style matches array, with 0 the entire match, and + * @param array $matches PCRE-style matches array, with 0 the entire match, and * either index 1, 2 or 3 set with a hex value, dec value, * or string (respectively). - * @returns Replacement string. + * @return string Replacement string. */ - protected function specialEntityCallback($matches) { + protected function specialEntityCallback($matches) + { $entity = $matches[0]; $is_num = (@$matches[0][1] === '#'); if ($is_num) { @@ -3769,7 +4551,6 @@ protected function specialEntityCallback($matches) { $entity; } } - } @@ -3792,16 +4573,46 @@ class HTMLPurifier_ErrorCollector const MESSAGE = 2; const CHILDREN = 3; + /** + * @type array + */ protected $errors; + + /** + * @type array + */ protected $_current; + + /** + * @type array + */ protected $_stacks = array(array()); + + /** + * @type HTMLPurifier_Language + */ protected $locale; + + /** + * @type HTMLPurifier_Generator + */ protected $generator; + + /** + * @type HTMLPurifier_Context + */ protected $context; + /** + * @type array + */ protected $lines = array(); - public function __construct($context) { + /** + * @param HTMLPurifier_Context $context + */ + public function __construct($context) + { $this->locale =& $context->get('Locale'); $this->context = $context; $this->_current =& $this->_stacks[0]; @@ -3810,13 +4621,11 @@ public function __construct($context) { /** * Sends an error message to the collector for later use - * @param $severity int Error severity, PHP error style (don't use E_USER_) - * @param $msg string Error message text - * @param $subst1 string First substitution for $msg - * @param $subst2 string ... + * @param int $severity Error severity, PHP error style (don't use E_USER_) + * @param string $msg Error message text */ - public function send($severity, $msg) { - + public function send($severity, $msg) + { $args = array(); if (func_num_args() > 2) { $args = func_get_args(); @@ -3826,7 +4635,7 @@ public function send($severity, $msg) { $token = $this->context->get('CurrentToken', true); $line = $token ? $token->line : $this->context->get('CurrentLine', true); - $col = $token ? $token->col : $this->context->get('CurrentCol', true); + $col = $token ? $token->col : $this->context->get('CurrentCol', true); $attr = $this->context->get('CurrentAttr', true); // perform special substitutions, also add custom parameters @@ -3836,7 +4645,9 @@ public function send($severity, $msg) { } if (!is_null($attr)) { $subst['$CurrentAttr.Name'] = $attr; - if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + if (isset($token->attr[$attr])) { + $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + } } if (empty($args)) { @@ -3845,7 +4656,9 @@ public function send($severity, $msg) { $msg = $this->locale->formatMessage($msg, $args); } - if (!empty($subst)) $msg = strtr($msg, $subst); + if (!empty($subst)) { + $msg = strtr($msg, $subst); + } // (numerically indexed) $error = array( @@ -3856,16 +4669,15 @@ public function send($severity, $msg) { ); $this->_current[] = $error; - // NEW CODE BELOW ... - - $struct = null; // Top-level errors are either: // TOKEN type, if $value is set appropriately, or // "syntax" type, if $value is null $new_struct = new HTMLPurifier_ErrorStruct(); $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN; - if ($token) $new_struct->value = clone $token; + if ($token) { + $new_struct->value = clone $token; + } if (is_int($line) && is_int($col)) { if (isset($this->lines[$line][$col])) { $struct = $this->lines[$line][$col]; @@ -3904,30 +4716,34 @@ public function send($severity, $msg) { /** * Retrieves raw error data for custom formatter to use - * @param List of arrays in format of array(line of error, - * error severity, error message, - * recursive sub-errors array) */ - public function getRaw() { + public function getRaw() + { return $this->errors; } /** * Default HTML formatting implementation for error messages - * @param $config Configuration array, vital for HTML output nature - * @param $errors Errors array to display; used for recursion. + * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature + * @param array $errors Errors array to display; used for recursion. + * @return string */ - public function getHTMLFormatted($config, $errors = null) { + public function getHTMLFormatted($config, $errors = null) + { $ret = array(); $this->generator = new HTMLPurifier_Generator($config, $this->context); - if ($errors === null) $errors = $this->errors; + if ($errors === null) { + $errors = $this->errors; + } // 'At line' message needs to be removed // generation code for new structure goes here. It needs to be recursive. foreach ($this->lines as $line => $col_array) { - if ($line == -1) continue; + if ($line == -1) { + continue; + } foreach ($col_array as $col => $struct) { $this->_renderStruct($ret, $struct, $line, $col); } @@ -3944,7 +4760,8 @@ public function getHTMLFormatted($config, $errors = null) { } - private function _renderStruct(&$ret, $struct, $line = null, $col = null) { + private function _renderStruct(&$ret, $struct, $line = null, $col = null) + { $stack = array($struct); $context_stack = array(array()); while ($current = array_pop($stack)) { @@ -3970,7 +4787,7 @@ private function _renderStruct(&$ret, $struct, $line = null, $col = null) { //$string .= '
    '; $ret[] = $string; } - foreach ($current->children as $type => $array) { + foreach ($current->children as $array) { $context[] = $current; $stack = array_merge($stack, array_reverse($array, true)); for ($i = count($array); $i > 0; $i--) { @@ -3979,7 +4796,6 @@ private function _renderStruct(&$ret, $struct, $line = null, $col = null) { } } } - } @@ -4005,6 +4821,7 @@ class HTMLPurifier_ErrorStruct /** * Type of this struct. + * @type string */ public $type; @@ -4014,11 +4831,13 @@ class HTMLPurifier_ErrorStruct * - TOKEN: Instance of HTMLPurifier_Token * - ATTR: array('attr-name', 'value') * - CSSPROP: array('prop-name', 'value') + * @type mixed */ public $value; /** * Errors registered for this structure. + * @type array */ public $errors = array(); @@ -4026,10 +4845,17 @@ class HTMLPurifier_ErrorStruct * Child ErrorStructs that are from this structure. For example, a TOKEN * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional * array in structure: [TYPE]['identifier'] + * @type array */ public $children = array(); - public function getChild($type, $id) { + /** + * @param string $type + * @param string $id + * @return mixed + */ + public function getChild($type, $id) + { if (!isset($this->children[$type][$id])) { $this->children[$type][$id] = new HTMLPurifier_ErrorStruct(); $this->children[$type][$id]->type = $type; @@ -4037,10 +4863,14 @@ public function getChild($type, $id) { return $this->children[$type][$id]; } - public function addError($severity, $message) { + /** + * @param int $severity + * @param string $message + */ + public function addError($severity, $message) + { $this->errors[] = array($severity, $message); } - } @@ -4083,24 +4913,34 @@ class HTMLPurifier_Filter { /** - * Name of the filter for identification purposes + * Name of the filter for identification purposes. + * @type string */ public $name; /** * Pre-processor function, handles HTML before HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string */ - public function preFilter($html, $config, $context) { + public function preFilter($html, $config, $context) + { return $html; } /** * Post-processor function, handles HTML after HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string */ - public function postFilter($html, $config, $context) { + public function postFilter($html, $config, $context) + { return $html; } - } @@ -4118,52 +4958,61 @@ class HTMLPurifier_Generator { /** - * Whether or not generator should produce XML output + * Whether or not generator should produce XML output. + * @type bool */ private $_xhtml = true; /** - * :HACK: Whether or not generator should comment the insides of )#si', - array($this, 'scriptCallback'), $html); + $html = preg_replace_callback( + '#(]*>)(\s*[^<].+?)()#si', + array($this, 'scriptCallback'), + $html + ); } $html = $this->normalize($html, $config, $context); @@ -15054,15 +18727,15 @@ public function tokenizeHTML($html, $config, $context) { if ($maintain_line_numbers) { $current_line = 1; - $current_col = 0; + $current_col = 0; $length = strlen($html); } else { $current_line = false; - $current_col = false; + $current_col = false; $length = false; } $context->register('CurrentLine', $current_line); - $context->register('CurrentCol', $current_col); + $context->register('CurrentCol', $current_col); $nl = "\n"; // how often to manually recalculate. This will ALWAYS be right, // but it's pretty wasteful. Set to 0 to turn off @@ -15076,16 +18749,14 @@ public function tokenizeHTML($html, $config, $context) { // for testing synchronization $loops = 0; - while(++$loops) { - + while (++$loops) { // $cursor is either at the start of a token, or inside of // a tag (i.e. there was a < immediately before it), as indicated // by $inside_tag if ($maintain_line_numbers) { - // $rcursor, however, is always at the start of a token. - $rcursor = $cursor - (int) $inside_tag; + $rcursor = $cursor - (int)$inside_tag; // Column number is cheap, so we calculate it every round. // We're interested at the *end* of the newline string, so @@ -15095,14 +18766,11 @@ public function tokenizeHTML($html, $config, $context) { $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); // recalculate lines - if ( - $synchronize_interval && // synchronization is on - $cursor > 0 && // cursor is further than zero - $loops % $synchronize_interval === 0 // time to synchronize! - ) { + if ($synchronize_interval && // synchronization is on + $cursor > 0 && // cursor is further than zero + $loops % $synchronize_interval === 0) { // time to synchronize! $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); } - } $position_next_lt = strpos($html, '<', $cursor); @@ -15118,35 +18786,42 @@ public function tokenizeHTML($html, $config, $context) { if (!$inside_tag && $position_next_lt !== false) { // We are not inside tag and there still is another tag to parse $token = new - HTMLPurifier_Token_Text( - $this->parseData( - substr( - $html, $cursor, $position_next_lt - $cursor - ) + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor, + $position_next_lt - $cursor ) - ); + ) + ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); } $array[] = $token; - $cursor = $position_next_lt + 1; + $cursor = $position_next_lt + 1; $inside_tag = true; continue; } elseif (!$inside_tag) { // We are not inside tag but there are no more tags // If we're already at the end, break - if ($cursor === strlen($html)) break; + if ($cursor === strlen($html)) { + break; + } // Create Text of rest of string $token = new - HTMLPurifier_Token_Text( - $this->parseData( - substr( - $html, $cursor - ) + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor ) - ); - if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col); + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } $array[] = $token; break; } elseif ($inside_tag && $position_next_gt !== false) { @@ -15170,16 +18845,16 @@ public function tokenizeHTML($html, $config, $context) { } // Check if it's a comment - if ( - substr($segment, 0, 3) === '!--' - ) { + if (substr($segment, 0, 3) === '!--') { // re-determine segment length, looking for --> $position_comment_end = strpos($html, '-->', $cursor); if ($position_comment_end === false) { // uh oh, we have a comment that extends to // infinity. Can't be helped: set comment // end position to end of string - if ($e) $e->send(E_WARNING, 'Lexer: Unclosed comment'); + if ($e) { + $e->send(E_WARNING, 'Lexer: Unclosed comment'); + } $position_comment_end = strlen($html); $end = true; } else { @@ -15188,11 +18863,13 @@ public function tokenizeHTML($html, $config, $context) { $strlen_segment = $position_comment_end - $cursor; $segment = substr($html, $cursor, $strlen_segment); $token = new - HTMLPurifier_Token_Comment( - substr( - $segment, 3, $strlen_segment - 3 - ) - ); + HTMLPurifier_Token_Comment( + substr( + $segment, + 3, + $strlen_segment - 3 + ) + ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); @@ -15204,7 +18881,7 @@ public function tokenizeHTML($html, $config, $context) { } // Check if it's an end tag - $is_end_tag = (strpos($segment,'/') === 0); + $is_end_tag = (strpos($segment, '/') === 0); if ($is_end_tag) { $type = substr($segment, 1); $token = new HTMLPurifier_Token_End($type); @@ -15223,7 +18900,9 @@ public function tokenizeHTML($html, $config, $context) { // text and go our merry way if (!ctype_alpha($segment[0])) { // XML: $segment[0] !== '_' && $segment[0] !== ':' - if ($e) $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + if ($e) { + $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + } $token = new HTMLPurifier_Token_Text('<'); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); @@ -15238,7 +18917,7 @@ public function tokenizeHTML($html, $config, $context) { // trailing slash. Remember, we could have a tag like
    , so // any later token processing scripts must convert improperly // classified EmptyTags from StartTags. - $is_self_closing = (strrpos($segment,'/') === $strlen_segment-1); + $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); if ($is_self_closing) { $strlen_segment--; $segment = substr($segment, 0, $strlen_segment); @@ -15268,14 +18947,16 @@ public function tokenizeHTML($html, $config, $context) { $attribute_string = trim( substr( - $segment, $position_first_space + $segment, + $position_first_space ) ); if ($attribute_string) { $attr = $this->parseAttributeString( - $attribute_string - , $config, $context - ); + $attribute_string, + $config, + $context + ); } else { $attr = array(); } @@ -15295,15 +18976,19 @@ public function tokenizeHTML($html, $config, $context) { continue; } else { // inside tag, but there's no ending > sign - if ($e) $e->send(E_WARNING, 'Lexer: Missing gt'); + if ($e) { + $e->send(E_WARNING, 'Lexer: Missing gt'); + } $token = new - HTMLPurifier_Token_Text( - '<' . - $this->parseData( - substr($html, $cursor) - ) - ); - if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col); + HTMLPurifier_Token_Text( + '<' . + $this->parseData( + substr($html, $cursor) + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } // no cursor scroll? Hmm... $array[] = $token; break; @@ -15318,8 +19003,14 @@ public function tokenizeHTML($html, $config, $context) { /** * PHP 5.0.x compatible substr_count that implements offset and length - */ - protected function substrCount($haystack, $needle, $offset, $length) { + * @param string $haystack + * @param string $needle + * @param int $offset + * @param int $length + * @return int + */ + protected function substrCount($haystack, $needle, $offset, $length) + { static $oldVersion; if ($oldVersion === null) { $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); @@ -15335,13 +19026,18 @@ protected function substrCount($haystack, $needle, $offset, $length) { /** * Takes the inside of an HTML tag and makes an assoc array of attributes. * - * @param $string Inside of tag excluding name. - * @returns Assoc array of attributes. + * @param string $string Inside of tag excluding name. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array Assoc array of attributes. */ - public function parseAttributeString($string, $config, $context) { - $string = (string) $string; // quick typecast + public function parseAttributeString($string, $config, $context) + { + $string = (string)$string; // quick typecast - if ($string == '') return array(); // no attributes + if ($string == '') { + return array(); + } // no attributes $e = false; if ($config->get('Core.CollectErrors')) { @@ -15360,46 +19056,55 @@ public function parseAttributeString($string, $config, $context) { list($key, $quoted_value) = explode('=', $string); $quoted_value = trim($quoted_value); if (!$key) { - if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key'); + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } return array(); } - if (!$quoted_value) return array($key => ''); + if (!$quoted_value) { + return array($key => ''); + } $first_char = @$quoted_value[0]; - $last_char = @$quoted_value[strlen($quoted_value)-1]; + $last_char = @$quoted_value[strlen($quoted_value) - 1]; $same_quote = ($first_char == $last_char); $open_quote = ($first_char == '"' || $first_char == "'"); - if ( $same_quote && $open_quote) { + if ($same_quote && $open_quote) { // well behaved $value = substr($quoted_value, 1, strlen($quoted_value) - 2); } else { // not well behaved if ($open_quote) { - if ($e) $e->send(E_ERROR, 'Lexer: Missing end quote'); + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing end quote'); + } $value = substr($quoted_value, 1); } else { $value = $quoted_value; } } - if ($value === false) $value = ''; + if ($value === false) { + $value = ''; + } return array($key => $this->parseData($value)); } // setup loop environment - $array = array(); // return assoc array of attributes + $array = array(); // return assoc array of attributes $cursor = 0; // current position in string (moves forward) - $size = strlen($string); // size of the string (stays the same) + $size = strlen($string); // size of the string (stays the same) // if we have unquoted attributes, the parser expects a terminating // space, so let's guarantee that there's always a terminating space. $string .= ' '; - while(true) { - - if ($cursor >= $size) { - break; + $old_cursor = -1; + while ($cursor < $size) { + if ($old_cursor >= $cursor) { + throw new Exception("Infinite loop detected"); } + $old_cursor = $cursor; $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); // grab the key @@ -15414,8 +19119,10 @@ public function parseAttributeString($string, $config, $context) { $key = substr($string, $key_begin, $key_end - $key_begin); if (!$key) { - if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key'); - $cursor += strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop continue; // empty key } @@ -15466,24 +19173,177 @@ public function parseAttributeString($string, $config, $context) { } $value = substr($string, $value_begin, $value_end - $value_begin); - if ($value === false) $value = ''; + if ($value === false) { + $value = ''; + } $array[$key] = $this->parseData($value); $cursor++; - } else { // boolattr if ($key !== '') { $array[$key] = $key; } else { // purely theoretical - if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key'); + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } } - } } return $array; } +} + + + + + +/** + * Concrete comment node class. + */ +class HTMLPurifier_Node_Comment extends HTMLPurifier_Node +{ + /** + * Character data within comment. + * @type string + */ + public $data; + + /** + * @type bool + */ + public $is_whitespace = true; + + /** + * Transparent constructor. + * + * @param string $data String comment data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); + } +} + + + +/** + * Concrete element node class. + */ +class HTMLPurifier_Node_Element extends HTMLPurifier_Node +{ + /** + * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. + * + * @note Strictly speaking, XML tags are case sensitive, so we shouldn't + * be lower-casing them, but these tokens cater to HTML tags, which are + * insensitive. + * @type string + */ + public $name; + + /** + * Associative array of the node's attributes. + * @type array + */ + public $attr = array(); + + /** + * List of child elements. + * @type array + */ + public $children = array(); + + /** + * Does this use the form or the form, i.e. + * is it a pair of start/end tokens or an empty token. + * @bool + */ + public $empty = false; + + public $endCol = null, $endLine = null, $endArmor = array(); + + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { + $this->name = $name; + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toTokenPair() { + // XXX inefficiency here, normalization is not necessary + if ($this->empty) { + return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null); + } else { + $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor); + $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor); + //$end->start = $start; + return array($start, $end); + } + } +} + + + + +/** + * Concrete text token class. + * + * Text tokens comprise of regular parsed character data (PCDATA) and raw + * character data (from the CDATA sections). Internally, their + * data is parsed with all entities expanded. Surprisingly, the text token + * does have a "tag name" called #PCDATA, which is how the DTD represents it + * in permissible child nodes. + */ +class HTMLPurifier_Node_Text extends HTMLPurifier_Node +{ + + /** + * PCDATA tag name compatible with DTD, see + * HTMLPurifier_ChildDef_Custom for details. + * @type string + */ + public $name = '#PCDATA'; + + /** + * @type string + */ + public $data; + /**< Parsed character data of text. */ + + /** + * @type bool + */ + public $is_whitespace; + + /**< Bool indicating if node is whitespace. */ + + /** + * Constructor, accepts data and determines if it is whitespace. + * @param string $data String parsed character data. + * @param int $line + * @param int $col + */ + public function __construct($data, $is_whitespace, $line = null, $col = null) + { + $this->data = $data; + $this->is_whitespace = $is_whitespace; + $this->line = $line; + $this->col = $col; + } + public function toTokenPair() { + return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null); + } } @@ -15498,16 +19358,23 @@ abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy /** * List of strategies to run tokens through. + * @type HTMLPurifier_Strategy[] */ protected $strategies = array(); - public function execute($tokens, $config, $context) { + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { foreach ($this->strategies as $strategy) { $tokens = $strategy->execute($tokens, $config, $context); } return $tokens; } - } @@ -15519,14 +19386,13 @@ public function execute($tokens, $config, $context) { */ class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite { - - public function __construct() { + public function __construct() + { $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); } - } @@ -15543,12 +19409,12 @@ public function __construct() { * document type definitions, such as the chameleon nature of ins/del * tags and global child exclusions. * - * The first major objective of this strategy is to iterate through all the - * nodes (not tokens) of the list of tokens and determine whether or not - * their children conform to the element's definition. If they do not, the - * child definition may optionally supply an amended list of elements that - * is valid or require that the entire node be deleted (and the previous - * node rescanned). + * The first major objective of this strategy is to iterate through all + * the nodes and determine whether or not their children conform to the + * element's definition. If they do not, the child definition may + * optionally supply an amended list of elements that is valid or + * require that the entire node be deleted (and the previous node + * rescanned). * * The second objective is to ensure that explicitly excluded elements of * an element do not appear in its children. Code that accomplishes this @@ -15558,43 +19424,34 @@ public function __construct() { * @note Whether or not unrecognized children are silently dropped or * translated into text depends on the child definitions. * - * @todo Enable nodes to be bubbled out of the structure. - * - * @warning This algorithm (though it may be hard to see) proceeds from - * a top-down fashion. Thus, parents are processed before - * children. This is easy to implement and has a nice effiency - * benefit, in that if a node is removed, we never waste any - * time processing it, but it also means that if a child - * changes in a non-encapsulated way (e.g. it is removed), we - * need to go back and reprocess the parent to see if those - * changes resulted in problems for the parent. See - * [BACKTRACK] for an example of this. In the current - * implementation, this backtracking can only be triggered when - * a node is removed and if that node was the sole node, the - * parent would need to be removed. As such, it is easy to see - * that backtracking only incurs constant overhead. If more - * sophisticated backtracking is implemented, care must be - * taken to avoid nontermination or exponential blowup. + * @todo Enable nodes to be bubbled out of the structure. This is + * easier with our new algorithm. */ class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy { - public function execute($tokens, $config, $context) { + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { + //####################################################################// // Pre-processing + // O(n) pass to convert to a tree, so that we can efficiently + // refer to substrings + $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context); + // get a copy of the HTML definition $definition = $config->getHTMLDefinition(); $excludes_enabled = !$config->get('Core.DisableExcludes'); - // insert implicit "parent" node, will be removed at end. - // DEFINITION CALL - $parent_name = $definition->info_parent; - array_unshift($tokens, new HTMLPurifier_Token_Start($parent_name)); - $tokens[] = new HTMLPurifier_Token_End($parent_name); - // setup the context variable 'IsInline', for chameleon processing // is 'false' when we are not inline, 'true' when it must always // be inline, and an integer when it is inline for a certain @@ -15608,272 +19465,116 @@ public function execute($tokens, $config, $context) { //####################################################################// // Loop initialization - // stack that contains the indexes of all parents, - // $stack[count($stack)-1] being the current parent - $stack = array(); - // stack that contains all elements that are excluded // it is organized by parent elements, similar to $stack, // but it is only populated when an element with exclusions is // processed, i.e. there won't be empty exclusions. - $exclude_stack = array(); + $exclude_stack = array($definition->info_parent_def->excludes); // variable that contains the start token while we are processing // nodes. This enables error reporting to do its job - $start_token = false; - $context->register('CurrentToken', $start_token); + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); //####################################################################// // Loop - // iterate through all start nodes. Determining the start node - // is complicated so it has been omitted from the loop construct - for ($i = 0, $size = count($tokens) ; $i < $size; ) { - - //################################################################// - // Gather information on children - - // child token accumulator - $child_tokens = array(); - - // scroll to the end of this node, report number, and collect - // all children - for ($j = $i, $depth = 0; ; $j++) { - if ($tokens[$j] instanceof HTMLPurifier_Token_Start) { - $depth++; - // skip token assignment on first iteration, this is the - // token we currently are on - if ($depth == 1) continue; - } elseif ($tokens[$j] instanceof HTMLPurifier_Token_End) { - $depth--; - // skip token assignment on last iteration, this is the - // end token of the token we're currently on - if ($depth == 0) break; - } - $child_tokens[] = $tokens[$j]; - } - - // $i is index of start token - // $j is index of end token - - $start_token = $tokens[$i]; // to make token available via CurrentToken - - //################################################################// - // Gather information on parent - - // calculate parent information - if ($count = count($stack)) { - $parent_index = $stack[$count-1]; - $parent_name = $tokens[$parent_index]->name; - if ($parent_index == 0) { - $parent_def = $definition->info_parent_def; - } else { - $parent_def = $definition->info[$parent_name]; - } - } else { - // processing as if the parent were the "root" node - // unknown info, it won't be used anyway, in the future, - // we may want to enforce one element only (this is - // necessary for HTML Purifier to clean entire documents - $parent_index = $parent_name = $parent_def = null; - } - - // calculate context - if ($is_inline === false) { - // check if conditions make it inline - if (!empty($parent_def) && $parent_def->descendants_are_inline) { - $is_inline = $count - 1; - } - } else { - // check if we're out of inline - if ($count === $is_inline) { - $is_inline = false; - } - } - - //################################################################// - // Determine whether element is explicitly excluded SGML-style + // We need to implement a post-order traversal iteratively, to + // avoid running into stack space limits. This is pretty tricky + // to reason about, so we just manually stack-ify the recursive + // variant: + // + // function f($node) { + // foreach ($node->children as $child) { + // f($child); + // } + // validate($node); + // } + // + // Thus, we will represent a stack frame as array($node, + // $is_inline, stack of children) + // e.g. array_reverse($node->children) - already processed + // children. + + $parent_def = $definition->info_parent_def; + $stack = array( + array($top_node, + $parent_def->descendants_are_inline, + $parent_def->excludes, // exclusions + 0) + ); - // determine whether or not element is excluded by checking all - // parent exclusions. The array should not be very large, two - // elements at most. - $excluded = false; - if (!empty($exclude_stack) && $excludes_enabled) { - foreach ($exclude_stack as $lookup) { - if (isset($lookup[$tokens[$i]->name])) { - $excluded = true; - // no need to continue processing - break; - } + while (!empty($stack)) { + list($node, $is_inline, $excludes, $ix) = array_pop($stack); + // recursive call + $go = false; + $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; + while (isset($node->children[$ix])) { + $child = $node->children[$ix++]; + if ($child instanceof HTMLPurifier_Node_Element) { + $go = true; + $stack[] = array($node, $is_inline, $excludes, $ix); + $stack[] = array($child, + // ToDo: I don't think it matters if it's def or + // child_def, but double check this... + $is_inline || $def->descendants_are_inline, + empty($def->excludes) ? $excludes + : array_merge($excludes, $def->excludes), + 0); + break; } - } - - //################################################################// - // Perform child validation - - if ($excluded) { - // there is an exclusion, remove the entire node - $result = false; - $excludes = array(); // not used, but good to initialize anyway + }; + if ($go) continue; + list($token, $d) = $node->toTokenPair(); + // base case + if ($excludes_enabled && isset($excludes[$node->name])) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); } else { - // DEFINITION CALL - if ($i === 0) { - // special processing for the first node - $def = $definition->info_parent_def; - } else { - $def = $definition->info[$tokens[$i]->name]; - + // XXX I suppose it would be slightly more efficient to + // avoid the allocation here and have children + // strategies handle it + $children = array(); + foreach ($node->children as $child) { + if (!$child->dead) $children[] = $child; } - - if (!empty($def->child)) { - // have DTD child def validate children - $result = $def->child->validateChildren( - $child_tokens, $config, $context); + $result = $def->child->validateChildren($children, $config, $context); + if ($result === true) { + // nop + $node->children = $children; + } elseif ($result === false) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); } else { - // weird, no child definition, get rid of everything - $result = false; - } - - // determine whether or not this element has any exclusions - $excludes = $def->excludes; - } - - // $result is now a bool or array - - //################################################################// - // Process result by interpreting $result - - if ($result === true || $child_tokens === $result) { - // leave the node as is - - // register start token as a parental node start - $stack[] = $i; - - // register exclusions if there are any - if (!empty($excludes)) $exclude_stack[] = $excludes; - - // move cursor to next possible start node - $i++; - - } elseif($result === false) { - // remove entire node - - if ($e) { - if ($excluded) { - $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); - } else { - $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); - } - } - - // calculate length of inner tokens and current tokens - $length = $j - $i + 1; - - // perform removal - array_splice($tokens, $i, $length); - - // update size - $size -= $length; - - // there is no start token to register, - // current node is now the next possible start node - // unless it turns out that we need to do a double-check - - // this is a rought heuristic that covers 100% of HTML's - // cases and 99% of all other cases. A child definition - // that would be tricked by this would be something like: - // ( | a b c) where it's all or nothing. Fortunately, - // our current implementation claims that that case would - // not allow empty, even if it did - if (!$parent_def->child->allow_empty) { - // we need to do a double-check [BACKTRACK] - $i = $parent_index; - array_pop($stack); - } - - // PROJECTED OPTIMIZATION: Process all children elements before - // reprocessing parent node. - - } else { - // replace node with $result - - // calculate length of inner tokens - $length = $j - $i - 1; - - if ($e) { - if (empty($result) && $length) { - $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); - } else { - $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); - } - } - - // perform replacement - array_splice($tokens, $i + 1, $length, $result); - - // update size - $size -= $length; - $size += count($result); - - // register start token as a parental node start - $stack[] = $i; - - // register exclusions if there are any - if (!empty($excludes)) $exclude_stack[] = $excludes; - - // move cursor to next possible start node - $i++; - - } - - //################################################################// - // Scroll to next start node - - // We assume, at this point, that $i is the index of the token - // that is the first possible new start point for a node. - - // Test if the token indeed is a start tag, if not, move forward - // and test again. - $size = count($tokens); - while ($i < $size and !$tokens[$i] instanceof HTMLPurifier_Token_Start) { - if ($tokens[$i] instanceof HTMLPurifier_Token_End) { - // pop a token index off the stack if we ended a node - array_pop($stack); - // pop an exclusion lookup off exclusion stack if - // we ended node and that node had exclusions - if ($i == 0 || $i == $size - 1) { - // use specialized var if it's the super-parent - $s_excludes = $definition->info_parent_def->excludes; - } else { - $s_excludes = $definition->info[$tokens[$i]->name]->excludes; - } - if ($s_excludes) { - array_pop($exclude_stack); + $node->children = $result; + if ($e) { + // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators + if (empty($result) && !empty($children)) { + $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + } else if ($result != $children) { + $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + } } } - $i++; } - } //####################################################################// // Post-processing - // remove implicit parent tokens at the beginning and end - array_shift($tokens); - array_pop($tokens); - // remove context variables $context->destroy('IsInline'); + $context->destroy('CurrentNode'); $context->destroy('CurrentToken'); //####################################################################// // Return - return $tokens; - + return HTMLPurifier_Arborize::flatten($node, $config, $context); } - } @@ -15896,66 +19597,83 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy /** * Array stream of tokens being processed. + * @type HTMLPurifier_Token[] */ protected $tokens; /** - * Current index in $tokens. + * Current token. + * @type HTMLPurifier_Token + */ + protected $token; + + /** + * Zipper managing the true state. + * @type HTMLPurifier_Zipper */ - protected $t; + protected $zipper; /** * Current nesting of elements. + * @type array */ protected $stack; /** * Injectors active in this stream processing. + * @type HTMLPurifier_Injector[] */ protected $injectors; /** * Current instance of HTMLPurifier_Config. + * @type HTMLPurifier_Config */ protected $config; /** * Current instance of HTMLPurifier_Context. + * @type HTMLPurifier_Context */ protected $context; - public function execute($tokens, $config, $context) { - + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + * @throws HTMLPurifier_Exception + */ + public function execute($tokens, $config, $context) + { $definition = $config->getHTMLDefinition(); // local variables $generator = new HTMLPurifier_Generator($config, $context); $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); // used for autoclose early abortion - $global_parent_allowed_elements = array(); - if (isset($definition->info[$definition->info_parent])) { - // may be unset under testing circumstances - $global_parent_allowed_elements = $definition->info[$definition->info_parent]->child->getAllowedElements($config); - } + $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config); $e = $context->get('ErrorCollector', true); - $t = false; // token index $i = false; // injector index - $token = false; // the current token - $reprocess = false; // whether or not to reprocess the same token + list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens); + if ($token === NULL) { + return array(); + } + $reprocess = false; // whether or not to reprocess the same token $stack = array(); // member variables - $this->stack =& $stack; - $this->t =& $t; - $this->tokens =& $tokens; - $this->config = $config; + $this->stack =& $stack; + $this->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; $this->context = $context; // context variables $context->register('CurrentNesting', $stack); - $context->register('InputIndex', $t); - $context->register('InputTokens', $tokens); - $context->register('CurrentToken', $token); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); // -- begin INJECTOR -- @@ -15967,9 +19685,13 @@ public function execute($tokens, $config, $context) { unset($injectors['Custom']); // special case foreach ($injectors as $injector => $b) { // XXX: Fix with a legitimate lookup table of enabled filters - if (strpos($injector, '.') !== false) continue; + if (strpos($injector, '.') !== false) { + continue; + } $injector = "HTMLPurifier_Injector_$injector"; - if (!$b) continue; + if (!$b) { + continue; + } $this->injectors[] = new $injector; } foreach ($def_injectors as $injector) { @@ -15977,7 +19699,9 @@ public function execute($tokens, $config, $context) { $this->injectors[] = $injector; } foreach ($custom_injectors as $injector) { - if (!$injector) continue; + if (!$injector) { + continue; + } if (is_string($injector)) { $injector = "HTMLPurifier_Injector_$injector"; $injector = new $injector; @@ -15989,7 +19713,9 @@ public function execute($tokens, $config, $context) { // variables for performance reasons foreach ($this->injectors as $ix => $injector) { $error = $injector->prepare($config, $context); - if (!$error) continue; + if (!$error) { + continue; + } array_splice($this->injectors, $ix, 1); // rm the injector trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); } @@ -16005,39 +19731,40 @@ public function execute($tokens, $config, $context) { // punt ($reprocess = true; continue;) and it does that for us. // isset is in loop because $tokens size changes during loop exec - for ( - $t = 0; - $t == 0 || isset($tokens[$t - 1]); - // only increment if we don't need to reprocess - $reprocess ? $reprocess = false : $t++ - ) { + for (;; + // only increment if we don't need to reprocess + $reprocess ? $reprocess = false : $token = $zipper->next($token)) { // check for a rewind - if (is_int($i) && $i >= 0) { + if (is_int($i)) { // possibility: disable rewinding if the current token has a // rewind set on it already. This would offer protection from // infinite loop, but might hinder some advanced rewinding. - $rewind_to = $this->injectors[$i]->getRewind(); - if (is_int($rewind_to) && $rewind_to < $t) { - if ($rewind_to < 0) $rewind_to = 0; - while ($t > $rewind_to) { - $t--; - $prev = $tokens[$t]; + $rewind_offset = $this->injectors[$i]->getRewindOffset(); + if (is_int($rewind_offset)) { + for ($j = 0; $j < $rewind_offset; $j++) { + if (empty($zipper->front)) break; + $token = $zipper->prev($token); // indicate that other injectors should not process this token, // but we need to reprocess it - unset($prev->skip[$i]); - $prev->rewind = $i; - if ($prev instanceof HTMLPurifier_Token_Start) array_pop($this->stack); - elseif ($prev instanceof HTMLPurifier_Token_End) $this->stack[] = $prev->start; + unset($token->skip[$i]); + $token->rewind = $i; + if ($token instanceof HTMLPurifier_Token_Start) { + array_pop($this->stack); + } elseif ($token instanceof HTMLPurifier_Token_End) { + $this->stack[] = $token->start; + } } } $i = false; } // handle case of document end - if (!isset($tokens[$t])) { + if ($token === NULL) { // kill processing if stack is empty - if (empty($this->stack)) break; + if (empty($this->stack)) { + break; + } // peek $top_nesting = array_pop($this->stack); @@ -16049,26 +19776,30 @@ public function execute($tokens, $config, $context) { } // append, don't splice, since this is the end - $tokens[] = new HTMLPurifier_Token_End($top_nesting->name); + $token = new HTMLPurifier_Token_End($top_nesting->name); // punt! $reprocess = true; continue; } - $token = $tokens[$t]; - - //echo '
    '; printTokens($tokens, $t); printTokens($this->stack); + //echo '
    '; printZipper($zipper, $token);//printTokens($this->stack); //flush(); // quick-check: if it's not a tag, no need to process if (empty($token->is_tag)) { if ($token instanceof HTMLPurifier_Token_Text) { foreach ($this->injectors as $i => $injector) { - if (isset($token->skip[$i])) continue; - if ($token->rewind !== null && $token->rewind !== $i) continue; - $injector->handleText($token); - $this->processToken($token, $i); + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); $reprocess = true; break; } @@ -16087,12 +19818,22 @@ public function execute($tokens, $config, $context) { $ok = false; if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { // claims to be a start tag but is empty - $token = new HTMLPurifier_Token_Empty($token->name, $token->attr, $token->line, $token->col, $token->armor); + $token = new HTMLPurifier_Token_Empty( + $token->name, + $token->attr, + $token->line, + $token->col, + $token->armor + ); $ok = true; } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { // claims to be empty but really is a start tag - $this->swap(new HTMLPurifier_Token_End($token->name)); - $this->insertBefore(new HTMLPurifier_Token_Start($token->name, $token->attr, $token->line, $token->col, $token->armor)); + // NB: this assignment is required + $old_token = $token; + $token = new HTMLPurifier_Token_End($token->name); + $token = $this->insertBefore( + new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor) + ); // punt (since we had to modify the input stream in a non-trivial way) $reprocess = true; continue; @@ -16121,31 +19862,32 @@ public function execute($tokens, $config, $context) { $parent = array_pop($this->stack); $this->stack[] = $parent; + $parent_def = null; + $parent_elements = null; + $autoclose = false; if (isset($definition->info[$parent->name])) { - $elements = $definition->info[$parent->name]->child->getAllowedElements($config); - $autoclose = !isset($elements[$token->name]); - } else { - $autoclose = false; + $parent_def = $definition->info[$parent->name]; + $parent_elements = $parent_def->child->getAllowedElements($config); + $autoclose = !isset($parent_elements[$token->name]); } if ($autoclose && $definition->info[$token->name]->wrap) { - // Check if an element can be wrapped by another - // element to make it valid in a context (for + // Check if an element can be wrapped by another + // element to make it valid in a context (for // example,
        needs a
      • in between) $wrapname = $definition->info[$token->name]->wrap; $wrapdef = $definition->info[$wrapname]; $elements = $wrapdef->child->getAllowedElements($config); - $parent_elements = $definition->info[$parent->name]->child->getAllowedElements($config); if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { $newtoken = new HTMLPurifier_Token_Start($wrapname); - $this->insertBefore($newtoken); + $token = $this->insertBefore($newtoken); $reprocess = true; continue; } } $carryover = false; - if ($autoclose && $definition->info[$parent->name]->formatting) { + if ($autoclose && $parent_def->formatting) { $carryover = true; } @@ -16175,15 +19917,6 @@ public function execute($tokens, $config, $context) { // errors need to be updated $new_token = new HTMLPurifier_Token_End($parent->name); $new_token->start = $parent; - if ($carryover) { - $element = clone $parent; - // [TagClosedAuto] - $element->armor['MakeWellFormed_TagClosedError'] = true; - $element->carryover = true; - $this->processToken(array($new_token, $token, $element)); - } else { - $this->insertBefore($new_token); - } // [TagClosedSuppress] if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { if (!$carryover) { @@ -16192,8 +19925,17 @@ public function execute($tokens, $config, $context) { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); } } + if ($carryover) { + $element = clone $parent; + // [TagClosedAuto] + $element->armor['MakeWellFormed_TagClosedError'] = true; + $element->carryover = true; + $token = $this->processToken(array($new_token, $token, $element)); + } else { + $token = $this->insertBefore($new_token); + } } else { - $this->remove(); + $token = $this->remove(); } $reprocess = true; continue; @@ -16205,20 +19947,26 @@ public function execute($tokens, $config, $context) { if ($ok) { foreach ($this->injectors as $i => $injector) { - if (isset($token->skip[$i])) continue; - if ($token->rewind !== null && $token->rewind !== $i) continue; - $injector->handleElement($token); - $this->processToken($token, $i); + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleElement($r); + $token = $this->processToken($r, $i); $reprocess = true; break; } if (!$reprocess) { // ah, nothing interesting happened; do normal processing - $this->swap($token); if ($token instanceof HTMLPurifier_Token_Start) { $this->stack[] = $token; } elseif ($token instanceof HTMLPurifier_Token_End) { - throw new HTMLPurifier_Exception('Improper handling of end tag in start code; possible error in MakeWellFormed'); + throw new HTMLPurifier_Exception( + 'Improper handling of end tag in start code; possible error in MakeWellFormed' + ); } } continue; @@ -16232,13 +19980,15 @@ public function execute($tokens, $config, $context) { // make sure that we have something open if (empty($this->stack)) { if ($escape_invalid_tags) { - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); - $this->swap(new HTMLPurifier_Token_Text( - $generator->generateFromToken($token) - )); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); } else { - $this->remove(); - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); } $reprocess = true; continue; @@ -16252,10 +20002,15 @@ public function execute($tokens, $config, $context) { if ($current_parent->name == $token->name) { $token->start = $current_parent; foreach ($this->injectors as $i => $injector) { - if (isset($token->skip[$i])) continue; - if ($token->rewind !== null && $token->rewind !== $i) continue; - $injector->handleEnd($token); - $this->processToken($token, $i); + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleEnd($r); + $token = $this->processToken($r, $i); $this->stack[] = $current_parent; $reprocess = true; break; @@ -16283,13 +20038,15 @@ public function execute($tokens, $config, $context) { // we didn't find the tag, so remove if ($skipped_tags === false) { if ($escape_invalid_tags) { - $this->swap(new HTMLPurifier_Token_Text( - $generator->generateFromToken($token) - )); - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); } else { - $this->remove(); - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); } $reprocess = true; continue; @@ -16322,18 +20079,17 @@ public function execute($tokens, $config, $context) { $replace[] = $element; } } - $this->processToken($replace); + $token = $this->processToken($replace); $reprocess = true; continue; } - $context->destroy('CurrentNesting'); - $context->destroy('InputTokens'); - $context->destroy('InputIndex'); $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); - unset($this->injectors, $this->stack, $this->tokens, $this->t); - return $tokens; + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); } /** @@ -16352,25 +20108,38 @@ public function execute($tokens, $config, $context) { * If $token is an integer, that number of tokens (with the first token * being the current one) will be deleted. * - * @param $token Token substitution value - * @param $injector Injector that performed the substitution; default is if + * @param HTMLPurifier_Token|array|int|bool $token Token substitution value + * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if * this is not an injector related operation. + * @throws HTMLPurifier_Exception */ - protected function processToken($token, $injector = -1) { - + protected function processToken($token, $injector = -1) + { // normalize forms of token - if (is_object($token)) $token = array(1, $token); - if (is_int($token)) $token = array($token); - if ($token === false) $token = array(1); - if (!is_array($token)) throw new HTMLPurifier_Exception('Invalid token type from injector'); - if (!is_int($token[0])) array_unshift($token, 1); - if ($token[0] === 0) throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + if (is_object($token)) { + $token = array(1, $token); + } + if (is_int($token)) { + $token = array($token); + } + if ($token === false) { + $token = array(1); + } + if (!is_array($token)) { + throw new HTMLPurifier_Exception('Invalid token type from injector'); + } + if (!is_int($token[0])) { + array_unshift($token, 1); + } + if ($token[0] === 0) { + throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + } // $token is now an array with the following form: // array(number nodes to delete, new node 1, new node 2, ...) $delete = array_shift($token); - $old = array_splice($this->tokens, $this->t, $delete, $token); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); if ($injector > -1) { // determine appropriate skips @@ -16381,32 +20150,32 @@ protected function processToken($token, $injector = -1) { } } + return $r; + } /** * Inserts a token before the current token. Cursor now points to * this token. You must reprocess after this. + * @param HTMLPurifier_Token $token */ - private function insertBefore($token) { - array_splice($this->tokens, $this->t, 0, array($token)); + private function insertBefore($token) + { + // NB not $this->zipper->insertBefore(), due to positioning + // differences + $splice = $this->zipper->splice($this->token, 0, array($token)); + + return $splice[1]; } /** * Removes current token. Cursor now points to new token occupying previously * occupied space. You must reprocess after this. */ - private function remove() { - array_splice($this->tokens, $this->t, 1); - } - - /** - * Swap current token with new token. Cursor points to new token (no - * change). You must reprocess after this. - */ - private function swap($token) { - $this->tokens[$this->t] = $token; + private function remove() + { + return $this->zipper->delete(); } - } @@ -16424,13 +20193,20 @@ private function swap($token) { class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy { - public function execute($tokens, $config, $context) { + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { $definition = $config->getHTMLDefinition(); $generator = new HTMLPurifier_Generator($config, $context); $result = array(); $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); - $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); + $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); // currently only used to determine if comments should be kept $trusted = $config->get('HTML.Trusted'); @@ -16439,7 +20215,7 @@ public function execute($tokens, $config, $context) { $check_comments = $comment_lookup !== array() || $comment_regexp !== null; $remove_script_contents = $config->get('Core.RemoveScriptContents'); - $hidden_elements = $config->get('Core.HiddenElements'); + $hidden_elements = $config->get('Core.HiddenElements'); // remove script contents compatibility if ($remove_script_contents === true) { @@ -16464,34 +20240,31 @@ public function execute($tokens, $config, $context) { $e =& $context->get('ErrorCollector'); } - foreach($tokens as $token) { + foreach ($tokens as $token) { if ($remove_until) { if (empty($token->is_tag) || $token->name !== $remove_until) { continue; } } - if (!empty( $token->is_tag )) { + if (!empty($token->is_tag)) { // DEFINITION CALL // before any processing, try to transform the element - if ( - isset($definition->info_tag_transform[$token->name]) - ) { + if (isset($definition->info_tag_transform[$token->name])) { $original_name = $token->name; // there is a transformation for this tag // DEFINITION CALL $token = $definition-> - info_tag_transform[$token->name]-> - transform($token, $config, $context); - if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + info_tag_transform[$token->name]->transform($token, $config, $context); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + } } if (isset($definition->info[$token->name])) { - // mostly everything's good, but // we need to make sure required attributes are in order - if ( - ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && + if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && $definition->info[$token->name]->required_attr && ($token->name != 'img' || $remove_invalid_img) // ensure config option still works ) { @@ -16504,7 +20277,13 @@ public function execute($tokens, $config, $context) { } } if (!$ok) { - if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', $name); + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } continue; } $token->armor['ValidateAttributes'] = true; @@ -16518,7 +20297,9 @@ public function execute($tokens, $config, $context) { } elseif ($escape_invalid_tags) { // invalid tag, generate HTML representation and insert in - if ($e) $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } $token = new HTMLPurifier_Token_Text( $generator->generateFromToken($token) ); @@ -16533,9 +20314,13 @@ public function execute($tokens, $config, $context) { } else { $remove_until = false; } - if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } } else { - if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } } continue; } @@ -16559,11 +20344,15 @@ public function execute($tokens, $config, $context) { $found_double_hyphen = true; $token->data = str_replace('--', '-', $token->data); } - if ($trusted || !empty($comment_lookup[trim($token->data)]) || ($comment_regexp !== NULL && preg_match($comment_regexp, trim($token->data)))) { + if ($trusted || !empty($comment_lookup[trim($token->data)]) || + ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) { // OK good if ($e) { if ($trailing_hyphen) { - $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'); + $e->send( + E_NOTICE, + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' + ); } if ($found_double_hyphen) { $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); @@ -16577,7 +20366,9 @@ public function execute($tokens, $config, $context) { } } else { // strip comments - if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } continue; } } elseif ($token instanceof HTMLPurifier_Token_Text) { @@ -16590,12 +20381,9 @@ public function execute($tokens, $config, $context) { // we removed tokens until the end, throw error $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until); } - $context->destroy('CurrentToken'); - return $result; } - } @@ -16609,8 +20397,14 @@ public function execute($tokens, $config, $context) { class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy { - public function execute($tokens, $config, $context) { - + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { // setup validator $validator = new HTMLPurifier_AttrValidator(); @@ -16621,21 +20415,21 @@ public function execute($tokens, $config, $context) { // only process tokens that have attributes, // namely start and empty tags - if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) continue; + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } // skip tokens that are armored - if (!empty($token->armor['ValidateAttributes'])) continue; + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } // note that we have no facilities here for removing tokens $validator->validateToken($token, $config, $context); - - $tokens[$key] = $token; // for PHP 4 } $context->destroy('CurrentToken'); - return $tokens; } - } @@ -16659,9 +20453,14 @@ public function execute($tokens, $config, $context) { */ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform { - + /** + * @type string + */ public $transform_to = 'span'; + /** + * @type array + */ protected $_size_lookup = array( '0' => 'xx-small', '1' => 'xx-small', @@ -16679,8 +20478,14 @@ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform '+4' => '300%' ); - public function transform($tag, $config, $context) { - + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token_End|string + */ + public function transform($tag, $config, $context) + { if ($tag instanceof HTMLPurifier_Token_End) { $new_tag = clone $tag; $new_tag->name = $this->transform_to; @@ -16707,17 +20512,23 @@ public function transform($tag, $config, $context) { // normalize large numbers if ($attr['size'] !== '') { if ($attr['size']{0} == '+' || $attr['size']{0} == '-') { - $size = (int) $attr['size']; - if ($size < -2) $attr['size'] = '-2'; - if ($size > 4) $attr['size'] = '+4'; + $size = (int)$attr['size']; + if ($size < -2) { + $attr['size'] = '-2'; + } + if ($size > 4) { + $attr['size'] = '+4'; + } } else { - $size = (int) $attr['size']; - if ($size > 7) $attr['size'] = '7'; + $size = (int)$attr['size']; + if ($size > 7) { + $attr['size'] = '7'; + } } } if (isset($this->_size_lookup[$attr['size']])) { $prepend_style .= 'font-size:' . - $this->_size_lookup[$attr['size']] . ';'; + $this->_size_lookup[$attr['size']] . ';'; } unset($attr['size']); } @@ -16733,7 +20544,6 @@ public function transform($tag, $config, $context) { $new_tag->attr = $attr; return $new_tag; - } } @@ -16748,19 +20558,29 @@ public function transform($tag, $config, $context) { */ class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform { - + /** + * @type string + */ protected $style; /** - * @param $transform_to Tag name to transform to. - * @param $style CSS style to add to the tag + * @param string $transform_to Tag name to transform to. + * @param string $style CSS style to add to the tag */ - public function __construct($transform_to, $style = null) { + public function __construct($transform_to, $style = null) + { $this->transform_to = $transform_to; $this->style = $style; } - public function transform($tag, $config, $context) { + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function transform($tag, $config, $context) + { $new_tag = clone $tag; $new_tag->name = $this->transform_to; if (!is_null($this->style) && @@ -16770,7 +20590,6 @@ public function transform($tag, $config, $context) { } return $new_tag; } - } @@ -16782,17 +20601,33 @@ public function transform($tag, $config, $context) { */ class HTMLPurifier_Token_Comment extends HTMLPurifier_Token { - public $data; /**< Character data within comment. */ + /** + * Character data within comment. + * @type string + */ + public $data; + + /** + * @type bool + */ public $is_whitespace = true; + /** * Transparent constructor. * - * @param $data String comment data. + * @param string $data String comment data. + * @param int $line + * @param int $col */ - public function __construct($data, $line = null, $col = null) { + public function __construct($data, $line = null, $col = null) + { $this->data = $data; $this->line = $line; - $this->col = $col; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); } } @@ -16803,13 +20638,14 @@ public function __construct($data, $line = null, $col = null) { /** * Abstract class of a tag token (start, end or empty), and its behavior. */ -class HTMLPurifier_Token_Tag extends HTMLPurifier_Token +abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token { /** * Static bool marker that indicates the class is a tag. * * This allows us to check objects with !empty($obj->is_tag) * without having to use a function call is_a(). + * @type bool */ public $is_tag = true; @@ -16819,21 +20655,27 @@ class HTMLPurifier_Token_Tag extends HTMLPurifier_Token * @note Strictly speaking, XML tags are case sensitive, so we shouldn't * be lower-casing them, but these tokens cater to HTML tags, which are * insensitive. + * @type string */ public $name; /** * Associative array of the tag's attributes. + * @type array */ public $attr = array(); /** * Non-overloaded constructor, which lower-cases passed tag name. * - * @param $name String name. - * @param $attr Associative array of attributes. - */ - public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { + * @param string $name String name. + * @param array $attr Associative array of attributes. + * @param int $line + * @param int $col + * @param array $armor + */ + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) + { $this->name = ctype_lower($name) ? $name : strtolower($name); foreach ($attr as $key => $value) { // normalization only necessary when key is not lowercase @@ -16849,9 +20691,13 @@ public function __construct($name, $attr = array(), $line = null, $col = null, $ } $this->attr = $attr; $this->line = $line; - $this->col = $col; + $this->col = $col; $this->armor = $armor; } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); + } } @@ -16863,7 +20709,11 @@ public function __construct($name, $attr = array(), $line = null, $col = null, $ */ class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag { - + public function toNode() { + $n = parent::toNode(); + $n->empty = true; + return $n; + } } @@ -16880,10 +20730,15 @@ class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag { /** - * Token that started this node. Added by MakeWellFormed. Please - * do not edit this! + * Token that started this node. + * Added by MakeWellFormed. Please do not edit this! + * @type HTMLPurifier_Token */ public $start; + + public function toNode() { + throw new Exception("HTMLPurifier_Token_End->toNode not supported!"); + } } @@ -16895,7 +20750,6 @@ class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag */ class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag { - } @@ -16914,22 +20768,42 @@ class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag class HTMLPurifier_Token_Text extends HTMLPurifier_Token { - public $name = '#PCDATA'; /**< PCDATA tag name compatible with DTD. */ - public $data; /**< Parsed character data of text. */ - public $is_whitespace; /**< Bool indicating if node is whitespace. */ + /** + * @type string + */ + public $name = '#PCDATA'; + /**< PCDATA tag name compatible with DTD. */ + + /** + * @type string + */ + public $data; + /**< Parsed character data of text. */ + + /** + * @type bool + */ + public $is_whitespace; + + /**< Bool indicating if node is whitespace. */ /** * Constructor, accepts data and determines if it is whitespace. - * - * @param $data String parsed character data. + * @param string $data String parsed character data. + * @param int $line + * @param int $col */ - public function __construct($data, $line = null, $col = null) { + public function __construct($data, $line = null, $col = null) + { $this->data = $data; $this->is_whitespace = ctype_space($data); $this->line = $line; - $this->col = $col; + $this->col = $col; } + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } } @@ -16938,19 +20812,50 @@ public function __construct($data, $line = null, $col = null) { class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'DisableExternal'; + + /** + * @type array + */ protected $ourHostParts = false; - public function prepare($config) { + + /** + * @param HTMLPurifier_Config $config + * @return void + */ + public function prepare($config) + { $our_host = $config->getDefinition('URI')->host; - if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host)); + if ($our_host !== null) { + $this->ourHostParts = array_reverse(explode('.', $our_host)); + } } - public function filter(&$uri, $config, $context) { - if (is_null($uri->host)) return true; - if ($this->ourHostParts === false) return false; + + /** + * @param HTMLPurifier_URI $uri Reference + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($uri->host)) { + return true; + } + if ($this->ourHostParts === false) { + return false; + } $host_parts = array_reverse(explode('.', $uri->host)); foreach ($this->ourHostParts as $i => $x) { - if (!isset($host_parts[$i])) return false; - if ($host_parts[$i] != $this->ourHostParts[$i]) return false; + if (!isset($host_parts[$i])) { + return false; + } + if ($host_parts[$i] != $this->ourHostParts[$i]) { + return false; + } } return true; } @@ -16962,9 +20867,22 @@ public function filter(&$uri, $config, $context) { class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal { + /** + * @type string + */ public $name = 'DisableExternalResources'; - public function filter(&$uri, $config, $context) { - if (!$context->get('EmbeddedURI', true)) return true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (!$context->get('EmbeddedURI', true)) { + return true; + } return parent::filter($uri, $config, $context); } } @@ -16975,8 +20893,19 @@ public function filter(&$uri, $config, $context) { class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'DisableResources'; - public function filter(&$uri, $config, $context) { + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { return !$context->get('EmbeddedURI', true); } } @@ -16991,14 +20920,35 @@ public function filter(&$uri, $config, $context) { // points are involved), but I'm not 100% sure class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'HostBlacklist'; + + /** + * @type array + */ protected $blacklist = array(); - public function prepare($config) { + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { $this->blacklist = $config->get('URI.HostBlacklist'); return true; } - public function filter(&$uri, $config, $context) { - foreach($this->blacklist as $blacklisted_host_fragment) { + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + foreach ($this->blacklist as $blacklisted_host_fragment) { if (strpos($uri->host, $blacklisted_host_fragment) !== false) { return false; } @@ -17015,14 +20965,35 @@ public function filter(&$uri, $config, $context) { class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'MakeAbsolute'; + + /** + * @type + */ protected $base; + + /** + * @type array + */ protected $basePathStack = array(); - public function prepare($config) { + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { $def = $config->getDefinition('URI'); $this->base = $def->base; if (is_null($this->base)) { - trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_WARNING); + trigger_error( + 'URI.MakeAbsolute is being ignored due to lack of ' . + 'value for URI.Base configuration', + E_USER_WARNING + ); return false; } $this->base->fragment = null; // fragment is invalid for base URI @@ -17032,19 +21003,29 @@ public function prepare($config) { $this->basePathStack = $stack; return true; } - public function filter(&$uri, $config, $context) { - if (is_null($this->base)) return true; // abort early - if ( - $uri->path === '' && is_null($uri->scheme) && - is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment) - ) { + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($this->base)) { + return true; + } // abort early + if ($uri->path === '' && is_null($uri->scheme) && + is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) { // reference to current document $uri = clone $this->base; return true; } if (!is_null($uri->scheme)) { // absolute URI already: don't change - if (!is_null($uri->host)) return true; + if (!is_null($uri->host)) { + return true; + } $scheme_obj = $uri->getSchemeObj($config, $context); if (!$scheme_obj) { // scheme not recognized @@ -17077,22 +21058,33 @@ public function filter(&$uri, $config, $context) { } // re-combine $uri->scheme = $this->base->scheme; - if (is_null($uri->userinfo)) $uri->userinfo = $this->base->userinfo; - if (is_null($uri->host)) $uri->host = $this->base->host; - if (is_null($uri->port)) $uri->port = $this->base->port; + if (is_null($uri->userinfo)) { + $uri->userinfo = $this->base->userinfo; + } + if (is_null($uri->host)) { + $uri->host = $this->base->host; + } + if (is_null($uri->port)) { + $uri->port = $this->base->port; + } return true; } /** * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array */ - private function _collapseStack($stack) { + private function _collapseStack($stack) + { $result = array(); $is_folder = false; for ($i = 0; isset($stack[$i]); $i++) { $is_folder = false; // absorb an internally duplicated slash - if ($stack[$i] == '' && $i && isset($stack[$i+1])) continue; + if ($stack[$i] == '' && $i && isset($stack[$i + 1])) { + continue; + } if ($stack[$i] == '..') { if (!empty($result)) { $segment = array_pop($result); @@ -17117,7 +21109,9 @@ private function _collapseStack($stack) { } $result[] = $stack[$i]; } - if ($is_folder) $result[] = ''; + if ($is_folder) { + $result[] = ''; + } return $result; } } @@ -17128,26 +21122,79 @@ private function _collapseStack($stack) { class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'Munge'; + + /** + * @type bool + */ public $post = true; - private $target, $parser, $doEmbed, $secretKey; + /** + * @type string + */ + private $target; + + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + /** + * @type bool + */ + private $doEmbed; + + /** + * @type string + */ + private $secretKey; + + /** + * @type array + */ protected $replace = array(); - public function prepare($config) { - $this->target = $config->get('URI.' . $this->name); - $this->parser = new HTMLPurifier_URIParser(); - $this->doEmbed = $config->get('URI.MungeResources'); + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->target = $config->get('URI.' . $this->name); + $this->parser = new HTMLPurifier_URIParser(); + $this->doEmbed = $config->get('URI.MungeResources'); $this->secretKey = $config->get('URI.MungeSecretKey'); + if ($this->secretKey && !function_exists('hash_hmac')) { + throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support."); + } return true; } - public function filter(&$uri, $config, $context) { - if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if ($context->get('EmbeddedURI', true) && !$this->doEmbed) { + return true; + } $scheme_obj = $uri->getSchemeObj($config, $context); - if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it - if (!$scheme_obj->browsable) return true; // ignore non-browseable schemes, since we can't munge those in a reasonable way - if ($uri->isBenign($config, $context)) return true; // don't redirect if a benign URL + if (!$scheme_obj) { + return true; + } // ignore unknown schemes, maybe another postfilter did it + if (!$scheme_obj->browsable) { + return true; + } // ignore non-browseable schemes, since we can't munge those in a reasonable way + if ($uri->isBenign($config, $context)) { + return true; + } // don't redirect if a benign URL $this->makeReplace($uri, $config, $context); $this->replace = array_map('rawurlencode', $this->replace); @@ -17156,12 +21203,20 @@ public function filter(&$uri, $config, $context) { $new_uri = $this->parser->parse($new_uri); // don't redirect if the target host is the same as the // starting host - if ($uri->host === $new_uri->host) return true; + if ($uri->host === $new_uri->host) { + return true; + } $uri = $new_uri; // overwrite return true; } - protected function makeReplace($uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + protected function makeReplace($uri, $config, $context) + { $string = $uri->toString(); // always available $this->replace['%s'] = $string; @@ -17171,9 +21226,10 @@ protected function makeReplace($uri, $config, $context) { $this->replace['%m'] = $context->get('CurrentAttr', true); $this->replace['%p'] = $context->get('CurrentCSSProperty', true); // not always available - if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string); + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } } - } @@ -17188,25 +21244,58 @@ protected function makeReplace($uri, $config, $context) { */ class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'SafeIframe'; + + /** + * @type bool + */ public $always_load = true; - protected $regexp = NULL; - // XXX: The not so good bit about how this is all setup now is we + + /** + * @type string + */ + protected $regexp = null; + + // XXX: The not so good bit about how this is all set up now is we // can't check HTML.SafeIframe in the 'prepare' step: we have to // defer till the actual filtering. - public function prepare($config) { + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { $this->regexp = $config->get('URI.SafeIframeRegexp'); return true; } - public function filter(&$uri, $config, $context) { + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { // check if filter not applicable - if (!$config->get('HTML.SafeIframe')) return true; + if (!$config->get('HTML.SafeIframe')) { + return true; + } // check if the filter should actually trigger - if (!$context->get('EmbeddedURI', true)) return true; + if (!$context->get('EmbeddedURI', true)) { + return true; + } $token = $context->get('CurrentToken', true); - if (!($token && $token->name == 'iframe')) return true; + if (!($token && $token->name == 'iframe')) { + return true; + } // check if we actually have some whitelists enabled - if ($this->regexp === null) return false; + if ($this->regexp === null) { + return false; + } // actually check the whitelists return preg_match($this->regexp, $uri->toString()); } @@ -17219,21 +21308,38 @@ public function filter(&$uri, $config, $context) { /** * Implements data: URI for base64 encoded images supported by GD. */ -class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ public $browsable = true; + + /** + * @type array + */ public $allowed_types = array( // you better write validation code for other types if you // decide to allow them 'image/jpeg' => true, 'image/gif' => true, 'image/png' => true, - ); + ); // this is actually irrelevant since we only write out the path // component + /** + * @type bool + */ public $may_omit_host = true; - public function doValidate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $result = explode(',', $uri->path, 2); $is_base64 = false; $charset = null; @@ -17242,7 +21348,7 @@ public function doValidate(&$uri, $config, $context) { list($metadata, $data) = $result; // do some legwork on the metadata $metas = explode(';', $metadata); - while(!empty($metas)) { + while (!empty($metas)) { $cur = array_shift($metas); if ($cur == 'base64') { $is_base64 = true; @@ -17251,10 +21357,14 @@ public function doValidate(&$uri, $config, $context) { if (substr($cur, 0, 8) == 'charset=') { // doesn't match if there are arbitrary spaces, but // whatever dude - if ($charset !== null) continue; // garbage + if ($charset !== null) { + continue; + } // garbage $charset = substr($cur, 8); // not used } else { - if ($content_type !== null) continue; // garbage + if ($content_type !== null) { + continue; + } // garbage $content_type = $cur; } } @@ -17286,7 +21396,9 @@ public function doValidate(&$uri, $config, $context) { $info = getimagesize($file); restore_error_handler(); unlink($file); - if ($info == false) return false; + if ($info == false) { + return false; + } $image_code = $info[2]; } else { trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); @@ -17295,7 +21407,9 @@ public function doValidate(&$uri, $config, $context) { if ($real_content_type != $content_type) { // we're nice guys; if the content type is something else we // support, change it over - if (empty($this->allowed_types[$real_content_type])) return false; + if (empty($this->allowed_types[$real_content_type])) { + return false; + } $content_type = $real_content_type; } // ok, it's kosher, rewrite what we need @@ -17308,40 +21422,56 @@ public function doValidate(&$uri, $config, $context) { return true; } - public function muteErrorHandler($errno, $errstr) {} - + /** + * @param int $errno + * @param string $errstr + */ + public function muteErrorHandler($errno, $errstr) + { + } } - /** * Validates file as defined by RFC 1630 and RFC 1738. */ -class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme { - - // Generally file:// URLs are not accessible from most - // machines, so placing them as an img src is incorrect. +class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme +{ + /** + * Generally file:// URLs are not accessible from most + * machines, so placing them as an img src is incorrect. + * @type bool + */ public $browsable = false; - // Basically the *only* URI scheme for which this is true, since - // accessing files on the local machine is very common. In fact, - // browsers on some operating systems don't understand the - // authority, though I hear it is used on Windows to refer to - // network shares. + /** + * Basically the *only* URI scheme for which this is true, since + * accessing files on the local machine is very common. In fact, + * browsers on some operating systems don't understand the + * authority, though I hear it is used on Windows to refer to + * network shares. + * @type bool + */ public $may_omit_host = true; - public function doValidate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { // Authentication method is not supported $uri->userinfo = null; // file:// makes no provisions for accessing the resource - $uri->port = null; + $uri->port = null; // While it seems to work on Firefox, the querystring has // no possible effect and is thus stripped. - $uri->query = null; + $uri->query = null; return true; } - } @@ -17351,14 +21481,32 @@ public function doValidate(&$uri, $config, $context) { /** * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738. */ -class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ public $default_port = 21; + + /** + * @type bool + */ public $browsable = true; // usually + + /** + * @type bool + */ public $hierarchical = true; - public function doValidate(&$uri, $config, $context) { - $uri->query = null; + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->query = null; // typecode check $semicolon_pos = strrpos($uri->path, ';'); // reverse @@ -17381,10 +21529,8 @@ public function doValidate(&$uri, $config, $context) { $uri->path = str_replace(';', '%3B', $uri->path); $uri->path .= $type_ret; } - return true; } - } @@ -17394,17 +21540,34 @@ public function doValidate(&$uri, $config, $context) { /** * Validates http (HyperText Transfer Protocol) as defined by RFC 2616 */ -class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ public $default_port = 80; + + /** + * @type bool + */ public $browsable = true; + + /** + * @type bool + */ public $hierarchical = true; - public function doValidate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $uri->userinfo = null; return true; } - } @@ -17414,11 +21577,16 @@ public function doValidate(&$uri, $config, $context) { /** * Validates https (Secure HTTP) according to http scheme. */ -class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http { - +class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http +{ + /** + * @type int + */ public $default_port = 443; + /** + * @type bool + */ public $secure = true; - } @@ -17434,19 +21602,32 @@ class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http { * @todo Filter allowed query parameters */ -class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ public $browsable = false; + + /** + * @type bool + */ public $may_omit_host = true; - public function doValidate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $uri->userinfo = null; $uri->host = null; $uri->port = null; // we need to validate path against RFC 2368's addr-spec return true; } - } @@ -17456,20 +21637,33 @@ public function doValidate(&$uri, $config, $context) { /** * Validates news (Usenet) as defined by generic RFC 1738 */ -class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ public $browsable = false; + + /** + * @type bool + */ public $may_omit_host = true; - public function doValidate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $uri->userinfo = null; - $uri->host = null; - $uri->port = null; - $uri->query = null; + $uri->host = null; + $uri->port = null; + $uri->query = null; // typecode check needed on path return true; } - } @@ -17479,17 +21673,30 @@ public function doValidate(&$uri, $config, $context) { /** * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 */ -class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ public $default_port = 119; + + /** + * @type bool + */ public $browsable = false; - public function doValidate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $uri->userinfo = null; - $uri->query = null; + $uri->query = null; return true; } - } @@ -17503,28 +21710,41 @@ public function doValidate(&$uri, $config, $context) { */ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser { - - protected function parseImplementation($var, $type, $allow_null) { - if ($allow_null && $var === null) return null; + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return array|bool|float|int|mixed|null|string + * @throws HTMLPurifier_VarParserException + */ + protected function parseImplementation($var, $type, $allow_null) + { + if ($allow_null && $var === null) { + return null; + } switch ($type) { // Note: if code "breaks" from the switch, it triggers a generic // exception to be thrown. Specific errors can be specifically // done here. - case self::MIXED : - case self::ISTRING : - case self::STRING : - case self::TEXT : - case self::ITEXT : + case self::MIXED: + case self::ISTRING: + case self::STRING: + case self::TEXT: + case self::ITEXT: return $var; - case self::INT : - if (is_string($var) && ctype_digit($var)) $var = (int) $var; + case self::INT: + if (is_string($var) && ctype_digit($var)) { + $var = (int)$var; + } return $var; - case self::FLOAT : - if ((is_string($var) && is_numeric($var)) || is_int($var)) $var = (float) $var; + case self::FLOAT: + if ((is_string($var) && is_numeric($var)) || is_int($var)) { + $var = (float)$var; + } return $var; - case self::BOOL : + case self::BOOL: if (is_int($var) && ($var === 0 || $var === 1)) { - $var = (bool) $var; + $var = (bool)$var; } elseif (is_string($var)) { if ($var == 'on' || $var == 'true' || $var == '1') { $var = true; @@ -17535,45 +21755,56 @@ protected function parseImplementation($var, $type, $allow_null) { } } return $var; - case self::ALIST : - case self::HASH : - case self::LOOKUP : + case self::ALIST: + case self::HASH: + case self::LOOKUP: if (is_string($var)) { // special case: technically, this is an array with // a single empty string item, but having an empty // array is more intuitive - if ($var == '') return array(); + if ($var == '') { + return array(); + } if (strpos($var, "\n") === false && strpos($var, "\r") === false) { // simplistic string to array method that only works // for simple lists of tag names or alphanumeric characters - $var = explode(',',$var); + $var = explode(',', $var); } else { $var = preg_split('/(,|[\n\r]+)/', $var); } // remove spaces - foreach ($var as $i => $j) $var[$i] = trim($j); + foreach ($var as $i => $j) { + $var[$i] = trim($j); + } if ($type === self::HASH) { // key:value,key2:value2 $nvar = array(); foreach ($var as $keypair) { $c = explode(':', $keypair, 2); - if (!isset($c[1])) continue; + if (!isset($c[1])) { + continue; + } $nvar[trim($c[0])] = trim($c[1]); } $var = $nvar; } } - if (!is_array($var)) break; + if (!is_array($var)) { + break; + } $keys = array_keys($var); if ($keys === array_keys($keys)) { - if ($type == self::ALIST) return $var; - elseif ($type == self::LOOKUP) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { $new = array(); foreach ($var as $key) { $new[$key] = true; } return $new; - } else break; + } else { + break; + } } if ($type === self::ALIST) { trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); @@ -17582,7 +21813,11 @@ protected function parseImplementation($var, $type, $allow_null) { if ($type === self::LOOKUP) { foreach ($var as $key => $value) { if ($value !== true) { - trigger_error("Lookup array has non-true value at key '$key'; maybe your input array was not indexed numerically", E_USER_WARNING); + trigger_error( + "Lookup array has non-true value at key '$key'; " . + "maybe your input array was not indexed numerically", + E_USER_WARNING + ); } $var[$key] = true; } @@ -17593,7 +21828,6 @@ protected function parseImplementation($var, $type, $allow_null) { } $this->errorGeneric($var, $type); } - } @@ -17608,11 +21842,24 @@ protected function parseImplementation($var, $type, $allow_null) { class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser { - protected function parseImplementation($var, $type, $allow_null) { + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return null|string + */ + protected function parseImplementation($var, $type, $allow_null) + { return $this->evalExpression($var); } - protected function evalExpression($expr) { + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { $var = null; $result = eval("\$var = $expr;"); if ($result === false) { @@ -17620,7 +21867,6 @@ protected function evalExpression($expr) { } return $var; } - } diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php index ca4e4b68dc..c094fa0b83 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php @@ -10,18 +10,23 @@ class HTMLPurifier_ConfigSchema_Interchange /** * Name of the application this schema is describing. + * @type string */ public $name; /** * Array of Directive ID => array(directive info) + * @type HTMLPurifier_ConfigSchema_Interchange_Directive[] */ public $directives = array(); /** * Adds a directive array to $directives + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + * @throws HTMLPurifier_ConfigSchema_Exception */ - public function addDirective($directive) { + public function addDirective($directive) + { if (isset($this->directives[$i = $directive->id->toString()])) { throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); } @@ -32,11 +37,11 @@ public function addDirective($directive) { * Convenience function to perform standard validation. Throws exception * on failed validation. */ - public function validate() { + public function validate() + { $validator = new HTMLPurifier_ConfigSchema_Validator(); return $validator->validate($this); } - } // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php index 3d499e5236..fe9b3268f4 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -5,21 +5,39 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder /** * Used for processing DEFAULT, nothing else. + * @type HTMLPurifier_VarParser */ protected $varParser; - public function __construct($varParser = null) { + /** + * @param HTMLPurifier_VarParser $varParser + */ + public function __construct($varParser = null) + { $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); } - public static function buildFromDirectory($dir = null) { - $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); + /** + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public static function buildFromDirectory($dir = null) + { + $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); $interchange = new HTMLPurifier_ConfigSchema_Interchange(); return $builder->buildDir($interchange, $dir); } - public function buildDir($interchange, $dir = null) { - if (!$dir) $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public function buildDir($interchange, $dir = null) + { + if (!$dir) { + $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; + } if (file_exists($dir . '/info.ini')) { $info = parse_ini_file($dir . '/info.ini'); $interchange->name = $info['name']; @@ -39,24 +57,30 @@ public function buildDir($interchange, $dir = null) { foreach ($files as $file) { $this->buildFile($interchange, $dir . '/' . $file); } - return $interchange; } - public function buildFile($interchange, $file) { + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $file + */ + public function buildFile($interchange, $file) + { $parser = new HTMLPurifier_StringHashParser(); $this->build( $interchange, - new HTMLPurifier_StringHash( $parser->parseFile($file) ) + new HTMLPurifier_StringHash($parser->parseFile($file)) ); } /** * Builds an interchange object based on a hash. - * @param $interchange HTMLPurifier_ConfigSchema_Interchange object to build - * @param $hash HTMLPurifier_ConfigSchema_StringHash source data + * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build + * @param HTMLPurifier_StringHash $hash source data + * @throws HTMLPurifier_ConfigSchema_Exception */ - public function build($interchange, $hash) { + public function build($interchange, $hash) + { if (!$hash instanceof HTMLPurifier_StringHash) { $hash = new HTMLPurifier_StringHash($hash); } @@ -75,7 +99,13 @@ public function build($interchange, $hash) { $this->_findUnused($hash); } - public function buildDirective($interchange, $hash) { + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param HTMLPurifier_StringHash $hash + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function buildDirective($interchange, $hash) + { $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); // These are required elements: @@ -84,7 +114,9 @@ public function buildDirective($interchange, $hash) { if (isset($hash['TYPE'])) { $type = explode('/', $hash->offsetGet('TYPE')); - if (isset($type[1])) $directive->typeAllowsNull = true; + if (isset($type[1])) { + $directive->typeAllowsNull = true; + } $directive->type = $type[0]; } else { throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); @@ -92,7 +124,11 @@ public function buildDirective($interchange, $hash) { if (isset($hash['DEFAULT'])) { try { - $directive->default = $this->varParser->parse($hash->offsetGet('DEFAULT'), $directive->type, $directive->typeAllowsNull); + $directive->default = $this->varParser->parse( + $hash->offsetGet('DEFAULT'), + $directive->type, + $directive->typeAllowsNull + ); } catch (HTMLPurifier_VarParserException $e) { throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'"); } @@ -139,34 +175,45 @@ public function buildDirective($interchange, $hash) { /** * Evaluates an array PHP code string without array() wrapper + * @param string $contents */ - protected function evalArray($contents) { - return eval('return array('. $contents .');'); + protected function evalArray($contents) + { + return eval('return array(' . $contents . ');'); } /** * Converts an array list into a lookup array. + * @param array $array + * @return array */ - protected function lookup($array) { + protected function lookup($array) + { $ret = array(); - foreach ($array as $val) $ret[$val] = true; + foreach ($array as $val) { + $ret[$val] = true; + } return $ret; } /** * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id * object based on a string Id. + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id */ - protected function id($id) { + protected function id($id) + { return HTMLPurifier_ConfigSchema_Interchange_Id::make($id); } /** * Triggers errors for any unused keys passed in the hash; such keys * may indicate typos, missing values, etc. - * @param $hash Instance of ConfigSchema_StringHash to check. + * @param HTMLPurifier_StringHash $hash Hash to check. */ - protected function _findUnused($hash) { + protected function _findUnused($hash) + { $accessed = $hash->getAccessed(); foreach ($hash as $k => $v) { if (!isset($accessed[$k])) { @@ -174,7 +221,6 @@ protected function _findUnused($hash) { } } } - } // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php index 5772493a7f..9f14444f37 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php @@ -12,36 +12,48 @@ class HTMLPurifier_ConfigSchema_Validator { /** - * Easy to access global objects. + * @type HTMLPurifier_ConfigSchema_Interchange */ - protected $interchange, $aliases; + protected $interchange; + + /** + * @type array + */ + protected $aliases; /** * Context-stack to provide easy to read error messages. + * @type array */ protected $context = array(); /** - * HTMLPurifier_VarParser to test default's type. + * to test default's type. + * @type HTMLPurifier_VarParser */ protected $parser; - public function __construct() { + public function __construct() + { $this->parser = new HTMLPurifier_VarParser(); } /** - * Validates a fully-formed interchange object. Throws an - * HTMLPurifier_ConfigSchema_Exception if there's a problem. + * Validates a fully-formed interchange object. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return bool */ - public function validate($interchange) { + public function validate($interchange) + { $this->interchange = $interchange; $this->aliases = array(); // PHP is a bit lax with integer <=> string conversions in // arrays, so we don't use the identical !== comparison foreach ($interchange->directives as $i => $directive) { $id = $directive->id->toString(); - if ($i != $id) $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); + if ($i != $id) { + $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); + } $this->validateDirective($directive); } return true; @@ -49,8 +61,10 @@ public function validate($interchange) { /** * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. + * @param HTMLPurifier_ConfigSchema_Interchange_Id $id */ - public function validateId($id) { + public function validateId($id) + { $id_string = $id->toString(); $this->context[] = "id '$id_string'"; if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { @@ -67,8 +81,10 @@ public function validateId($id) { /** * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirective($d) { + public function validateDirective($d) + { $id = $d->id->toString(); $this->context[] = "directive '$id'"; $this->validateId($d->id); @@ -108,9 +124,13 @@ public function validateDirective($d) { /** * Extra validation if $allowed member variable of * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirectiveAllowed($d) { - if (is_null($d->allowed)) return; + public function validateDirectiveAllowed($d) + { + if (is_null($d->allowed)) { + return; + } $this->with($d, 'allowed') ->assertNotEmpty() ->assertIsLookup(); // handled by InterchangeBuilder @@ -119,7 +139,9 @@ public function validateDirectiveAllowed($d) { } $this->context[] = 'allowed'; foreach ($d->allowed as $val => $x) { - if (!is_string($val)) $this->error("value $val", 'must be a string'); + if (!is_string($val)) { + $this->error("value $val", 'must be a string'); + } } array_pop($this->context); } @@ -127,15 +149,23 @@ public function validateDirectiveAllowed($d) { /** * Extra validation if $valueAliases member variable of * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirectiveValueAliases($d) { - if (is_null($d->valueAliases)) return; + public function validateDirectiveValueAliases($d) + { + if (is_null($d->valueAliases)) { + return; + } $this->with($d, 'valueAliases') ->assertIsArray(); // handled by InterchangeBuilder $this->context[] = 'valueAliases'; foreach ($d->valueAliases as $alias => $real) { - if (!is_string($alias)) $this->error("alias $alias", 'must be a string'); - if (!is_string($real)) $this->error("alias target $real from alias '$alias'", 'must be a string'); + if (!is_string($alias)) { + $this->error("alias $alias", 'must be a string'); + } + if (!is_string($real)) { + $this->error("alias target $real from alias '$alias'", 'must be a string'); + } if ($alias === $real) { $this->error("alias '$alias'", "must not be an alias to itself"); } @@ -155,8 +185,10 @@ public function validateDirectiveValueAliases($d) { /** * Extra validation if $aliases member variable of * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirectiveAliases($d) { + public function validateDirectiveAliases($d) + { $this->with($d, 'aliases') ->assertIsArray(); // handled by InterchangeBuilder $this->context[] = 'aliases'; @@ -180,27 +212,37 @@ public function validateDirectiveAliases($d) { /** * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom * for validating simple member variables of objects. + * @param $obj + * @param $member + * @return HTMLPurifier_ConfigSchema_ValidatorAtom */ - protected function with($obj, $member) { + protected function with($obj, $member) + { return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); } /** * Emits an error, providing helpful context. + * @throws HTMLPurifier_ConfigSchema_Exception */ - protected function error($target, $msg) { - if ($target !== false) $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); - else $prefix = ucfirst($this->getFormattedContext()); + protected function error($target, $msg) + { + if ($target !== false) { + $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); + } else { + $prefix = ucfirst($this->getFormattedContext()); + } throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); } /** * Returns a formatted context string. + * @return string */ - protected function getFormattedContext() { + protected function getFormattedContext() + { return implode(' in ', array_reverse($this->context)); } - } // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php index 7bb887bc41..a2e0b4a1b3 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -8,59 +8,123 @@ */ class HTMLPurifier_ConfigSchema_ValidatorAtom { + /** + * @type string + */ + protected $context; - protected $context, $obj, $member, $contents; + /** + * @type object + */ + protected $obj; - public function __construct($context, $obj, $member) { - $this->context = $context; - $this->obj = $obj; - $this->member = $member; - $this->contents =& $obj->$member; + /** + * @type string + */ + protected $member; + + /** + * @type mixed + */ + protected $contents; + + public function __construct($context, $obj, $member) + { + $this->context = $context; + $this->obj = $obj; + $this->member = $member; + $this->contents =& $obj->$member; } - public function assertIsString() { - if (!is_string($this->contents)) $this->error('must be a string'); + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsString() + { + if (!is_string($this->contents)) { + $this->error('must be a string'); + } return $this; } - public function assertIsBool() { - if (!is_bool($this->contents)) $this->error('must be a boolean'); + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsBool() + { + if (!is_bool($this->contents)) { + $this->error('must be a boolean'); + } return $this; } - public function assertIsArray() { - if (!is_array($this->contents)) $this->error('must be an array'); + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsArray() + { + if (!is_array($this->contents)) { + $this->error('must be an array'); + } return $this; } - public function assertNotNull() { - if ($this->contents === null) $this->error('must not be null'); + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotNull() + { + if ($this->contents === null) { + $this->error('must not be null'); + } return $this; } - public function assertAlnum() { + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertAlnum() + { $this->assertIsString(); - if (!ctype_alnum($this->contents)) $this->error('must be alphanumeric'); + if (!ctype_alnum($this->contents)) { + $this->error('must be alphanumeric'); + } return $this; } - public function assertNotEmpty() { - if (empty($this->contents)) $this->error('must not be empty'); + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotEmpty() + { + if (empty($this->contents)) { + $this->error('must not be empty'); + } return $this; } - public function assertIsLookup() { + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsLookup() + { $this->assertIsArray(); foreach ($this->contents as $v) { - if ($v !== true) $this->error('must be a lookup array'); + if ($v !== true) { + $this->error('must be a lookup array'); + } } return $this; } - protected function error($msg) { + /** + * @param string $msg + * @throws HTMLPurifier_ConfigSchema_Exception + */ + protected function error($msg) + { throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); } - } // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser index fa0bacb947..22ea32185d 100644 Binary files a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser and b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser differ diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt new file mode 100644 index 0000000000..2c910cc7de --- /dev/null +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt @@ -0,0 +1,16 @@ +Core.AllowHostnameUnderscore +TYPE: bool +VERSION: 4.6.0 +DEFAULT: false +--DESCRIPTION-- +

        + By RFC 1123, underscores are not permitted in host names. + (This is in contrast to the specification for DNS, RFC + 2181, which allows underscores.) + However, most browsers do the right thing when faced with + an underscore in the host name, and so some poorly written + websites are written with the expectation this should work. + Setting this parameter to true relaxes our allowed character + check so that underscores are permitted. +

        +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt index be932b1cf7..1cc3fcda2f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt @@ -2,9 +2,11 @@ Core.EscapeInvalidChildren TYPE: bool DEFAULT: false --DESCRIPTION-- -When true, a child is found that is not allowed in the context of the +

        Warning: this configuration option is no longer does anything as of 4.6.0.

        + +

        When true, a child is found that is not allowed in the context of the parent element will be transformed into text as if it were ASCII. When false, that element and all internal tags will be dropped, though text will be preserved. There is no option for dropping the element but preserving -child nodes. +child nodes.

        --# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt index 3b50c8c131..123b6e26b8 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -11,7 +11,7 @@ DEFAULT: NULL to check if a URI has passed through HTML Purifier with this line:

        -
        $checksum === sha1($secret_key . ':' . $url)
        +
        $checksum === hash_hmac("sha256", $url, $secret_key)

        If the output is TRUE, the redirector script should accept the URI. diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php index 0f9bd73d91..1fa30bdfed 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php @@ -4,60 +4,52 @@ $messages = array( -'HTMLPurifier' => 'HTML Purifier', - + 'HTMLPurifier' => 'HTML Purifier', // for unit testing purposes -'LanguageFactoryTest: Pizza' => 'Pizza', -'LanguageTest: List' => '$1', -'LanguageTest: Hash' => '$1.Keys; $1.Values', - -'Item separator' => ', ', -'Item separator last' => ' and ', // non-Harvard style - -'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', -'ErrorCollector: At line' => ' at line $line', -'ErrorCollector: Incidental errors' => 'Incidental errors', - -'Lexer: Unclosed comment' => 'Unclosed comment', -'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', -'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', -'Lexer: Missing attribute key' => 'Attribute declaration has no key', -'Lexer: Missing end quote' => 'Attribute declaration has no end quote', -'Lexer: Extracted body' => 'Removed document metadata tags', - -'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', -'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', -'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text', -'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed', -'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed', -'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed', -'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end', -'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', -'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', - -'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', -'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', -'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', -'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', -'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', -'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', -'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', -'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', - -'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', -'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', -'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', -'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', - -'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', -'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', - + 'LanguageFactoryTest: Pizza' => 'Pizza', + 'LanguageTest: List' => '$1', + 'LanguageTest: Hash' => '$1.Keys; $1.Values', + 'Item separator' => ', ', + 'Item separator last' => ' and ', // non-Harvard style + + 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', + 'ErrorCollector: At line' => ' at line $line', + 'ErrorCollector: Incidental errors' => 'Incidental errors', + 'Lexer: Unclosed comment' => 'Unclosed comment', + 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', + 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', + 'Lexer: Missing attribute key' => 'Attribute declaration has no key', + 'Lexer: Missing end quote' => 'Attribute declaration has no end quote', + 'Lexer: Extracted body' => 'Removed document metadata tags', + 'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', + 'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', + 'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text', + 'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed', + 'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed', + 'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed', + 'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end', + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', + 'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', + 'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', + 'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', + 'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', + 'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', + 'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', + 'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', + 'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', + 'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', ); $errorNames = array( - E_ERROR => 'Error', + E_ERROR => 'Error', E_WARNING => 'Warning', - E_NOTICE => 'Notice' + E_NOTICE => 'Notice' ); // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php index 84e86116f0..16acd41574 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php @@ -7,25 +7,30 @@ class HTMLPurifier_Printer { /** - * Instance of HTMLPurifier_Generator for HTML generation convenience funcs + * For HTML generation convenience funcs. + * @type HTMLPurifier_Generator */ protected $generator; /** - * Instance of HTMLPurifier_Config, for easy access + * For easy access. + * @type HTMLPurifier_Config */ protected $config; /** * Initialize $generator. */ - public function __construct() { + public function __construct() + { } /** * Give generator necessary configuration if possible + * @param HTMLPurifier_Config $config */ - public function prepareGenerator($config) { + public function prepareGenerator($config) + { $all = $config->getAll(); $context = new HTMLPurifier_Context(); $this->generator = new HTMLPurifier_Generator($config, $context); @@ -39,45 +44,62 @@ public function prepareGenerator($config) { /** * Returns a start tag - * @param $tag Tag name - * @param $attr Attribute array + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string */ - protected function start($tag, $attr = array()) { + protected function start($tag, $attr = array()) + { return $this->generator->generateFromToken( - new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) - ); + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); } /** - * Returns an end teg - * @param $tag Tag name + * Returns an end tag + * @param string $tag Tag name + * @return string */ - protected function end($tag) { + protected function end($tag) + { return $this->generator->generateFromToken( - new HTMLPurifier_Token_End($tag) - ); + new HTMLPurifier_Token_End($tag) + ); } /** * Prints a complete element with content inside - * @param $tag Tag name - * @param $contents Element contents - * @param $attr Tag attributes - * @param $escape Bool whether or not to escape contents + * @param string $tag Tag name + * @param string $contents Element contents + * @param array $attr Tag attributes + * @param bool $escape whether or not to escape contents + * @return string */ - protected function element($tag, $contents, $attr = array(), $escape = true) { + protected function element($tag, $contents, $attr = array(), $escape = true) + { return $this->start($tag, $attr) . - ($escape ? $this->escape($contents) : $contents) . - $this->end($tag); + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); } - protected function elementEmpty($tag, $attr = array()) { + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { return $this->generator->generateFromToken( new HTMLPurifier_Token_Empty($tag, $attr) ); } - protected function text($text) { + /** + * @param string $text + * @return string + */ + protected function text($text) + { return $this->generator->generateFromToken( new HTMLPurifier_Token_Text($text) ); @@ -85,24 +107,29 @@ protected function text($text) { /** * Prints a simple key/value row in a table. - * @param $name Key - * @param $value Value + * @param string $name Key + * @param mixed $value Value + * @return string */ - protected function row($name, $value) { - if (is_bool($value)) $value = $value ? 'On' : 'Off'; + protected function row($name, $value) + { + if (is_bool($value)) { + $value = $value ? 'On' : 'Off'; + } return $this->start('tr') . "\n" . - $this->element('th', $name) . "\n" . - $this->element('td', $value) . "\n" . - $this->end('tr') - ; + $this->element('th', $name) . "\n" . + $this->element('td', $value) . "\n" . + $this->end('tr'); } /** * Escapes a string for HTML output. - * @param $string String to escape + * @param string $string String to escape + * @return string */ - protected function escape($string) { + protected function escape($string) + { $string = HTMLPurifier_Encoder::cleanUTF8($string); $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); return $string; @@ -110,32 +137,46 @@ protected function escape($string) { /** * Takes a list of strings and turns them into a single list - * @param $array List of strings - * @param $polite Bool whether or not to add an end before the last + * @param string[] $array List of strings + * @param bool $polite Bool whether or not to add an end before the last + * @return string */ - protected function listify($array, $polite = false) { - if (empty($array)) return 'None'; + protected function listify($array, $polite = false) + { + if (empty($array)) { + return 'None'; + } $ret = ''; $i = count($array); foreach ($array as $value) { $i--; $ret .= $value; - if ($i > 0 && !($polite && $i == 1)) $ret .= ', '; - if ($polite && $i == 1) $ret .= 'and '; + if ($i > 0 && !($polite && $i == 1)) { + $ret .= ', '; + } + if ($polite && $i == 1) { + $ret .= 'and '; + } } return $ret; } /** * Retrieves the class of an object without prefixes, as well as metadata - * @param $obj Object to determine class of - * @param $prefix Further prefix to remove + * @param object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string */ - protected function getClass($obj, $sec_prefix = '') { + protected function getClass($obj, $sec_prefix = '') + { static $five = null; - if ($five === null) $five = version_compare(PHP_VERSION, '5', '>='); + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } $prefix = 'HTMLPurifier_' . $sec_prefix; - if (!$five) $prefix = strtolower($prefix); + if (!$five) { + $prefix = strtolower($prefix); + } $class = str_replace($prefix, '', get_class($obj)); $lclass = strtolower($class); $class .= '('; @@ -164,13 +205,14 @@ protected function getClass($obj, $sec_prefix = '') { break; case 'css_importantdecorator': $class .= $this->getClass($obj->def, $sec_prefix); - if ($obj->allow) $class .= ', !important'; + if ($obj->allow) { + $class .= ', !important'; + } break; } $class .= ')'; return $class; } - } // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php index 0714580cf3..afc8c18ab9 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php @@ -2,10 +2,17 @@ class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer { - + /** + * @type HTMLPurifier_CSSDefinition + */ protected $def; - public function render($config) { + /** + * @param HTMLPurifier_Config $config + * @return string + */ + public function render($config) + { $this->def = $config->getCSSDefinition(); $ret = ''; @@ -32,7 +39,6 @@ public function render($config) { return $ret; } - } // vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php index b41476f941..660960f379 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php @@ -7,17 +7,20 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer { /** - * Printers for specific fields + * Printers for specific fields. + * @type HTMLPurifier_Printer[] */ protected $fields = array(); /** - * Documentation URL, can have fragment tagged on end + * Documentation URL, can have fragment tagged on end. + * @type string */ protected $docURL; /** - * Name of form element to stuff config in + * Name of form element to stuff config in. + * @type string */ protected $name; @@ -25,24 +28,27 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer * Whether or not to compress directive names, clipping them off * after a certain amount of letters. False to disable or integer letters * before clipping. + * @type bool */ protected $compress = false; /** - * @param $name Form element name for directives to be stuffed into - * @param $doc_url String documentation URL, will have fragment tagged on - * @param $compress Integer max length before compressing a directive name, set to false to turn off + * @param string $name Form element name for directives to be stuffed into + * @param string $doc_url String documentation URL, will have fragment tagged on + * @param bool $compress Integer max length before compressing a directive name, set to false to turn off */ public function __construct( - $name, $doc_url = null, $compress = false + $name, + $doc_url = null, + $compress = false ) { parent::__construct(); $this->docURL = $doc_url; - $this->name = $name; + $this->name = $name; $this->compress = $compress; // initialize sub-printers - $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); - $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); + $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); + $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); } /** @@ -50,32 +56,42 @@ public function __construct( * @param $cols Integer columns of textarea, null to use default * @param $rows Integer rows of textarea, null to use default */ - public function setTextareaDimensions($cols = null, $rows = null) { - if ($cols) $this->fields['default']->cols = $cols; - if ($rows) $this->fields['default']->rows = $rows; + public function setTextareaDimensions($cols = null, $rows = null) + { + if ($cols) { + $this->fields['default']->cols = $cols; + } + if ($rows) { + $this->fields['default']->rows = $rows; + } } /** * Retrieves styling, in case it is not accessible by webserver */ - public static function getCSS() { + public static function getCSS() + { return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css'); } /** * Retrieves JavaScript, in case it is not accessible by webserver */ - public static function getJavaScript() { + public static function getJavaScript() + { return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); } /** * Returns HTML output for a configuration form - * @param $config Configuration object of current form state, or an array + * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array * where [0] has an HTML namespace and [1] is being rendered. - * @param $allowed Optional namespace(s) and directives to restrict form to. + * @param array|bool $allowed Optional namespace(s) and directives to restrict form to. + * @param bool $render_controls + * @return string */ - public function render($config, $allowed = true, $render_controls = true) { + public function render($config, $allowed = true, $render_controls = true) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -91,29 +107,29 @@ public function render($config, $allowed = true, $render_controls = true) { $all = array(); foreach ($allowed as $key) { list($ns, $directive) = $key; - $all[$ns][$directive] = $config->get($ns .'.'. $directive); + $all[$ns][$directive] = $config->get($ns . '.' . $directive); } $ret = ''; $ret .= $this->start('table', array('class' => 'hp-config')); $ret .= $this->start('thead'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); - $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); + $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); + $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); $ret .= $this->end('tr'); $ret .= $this->end('thead'); foreach ($all as $ns => $directives) { $ret .= $this->renderNamespace($ns, $directives); } if ($render_controls) { - $ret .= $this->start('tbody'); - $ret .= $this->start('tr'); - $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); - $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); - $ret .= '[Reset]'; - $ret .= $this->end('td'); - $ret .= $this->end('tr'); - $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + $ret .= $this->start('tr'); + $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); + $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); + $ret .= '[Reset]'; + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); } $ret .= $this->end('table'); return $ret; @@ -122,13 +138,15 @@ public function render($config, $allowed = true, $render_controls = true) { /** * Renders a single namespace * @param $ns String namespace name - * @param $directive Associative array of directives to values + * @param array $directives array of directives to values + * @return string */ - protected function renderNamespace($ns, $directives) { + protected function renderNamespace($ns, $directives) + { $ret = ''; $ret .= $this->start('tbody', array('class' => 'namespace')); $ret .= $this->start('tr'); - $ret .= $this->element('th', $ns, array('colspan' => 2)); + $ret .= $this->element('th', $ns, array('colspan' => 2)); $ret .= $this->end('tr'); $ret .= $this->end('tbody'); $ret .= $this->start('tbody'); @@ -139,40 +157,44 @@ protected function renderNamespace($ns, $directives) { $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL); $ret .= $this->start('a', array('href' => $url)); } - $attr = array('for' => "{$this->name}:$ns.$directive"); - - // crop directive name if it's too long - if (!$this->compress || (strlen($directive) < $this->compress)) { - $directive_disp = $directive; - } else { - $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; - $attr['title'] = $directive; - } + $attr = array('for' => "{$this->name}:$ns.$directive"); + + // crop directive name if it's too long + if (!$this->compress || (strlen($directive) < $this->compress)) { + $directive_disp = $directive; + } else { + $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; + $attr['title'] = $directive; + } - $ret .= $this->element( - 'label', - $directive_disp, - // component printers must create an element with this id - $attr - ); - if ($this->docURL) $ret .= $this->end('a'); + $ret .= $this->element( + 'label', + $directive_disp, + // component printers must create an element with this id + $attr + ); + if ($this->docURL) { + $ret .= $this->end('a'); + } $ret .= $this->end('th'); $ret .= $this->start('td'); - $def = $this->config->def->info["$ns.$directive"]; - if (is_int($def)) { - $allow_null = $def < 0; - $type = abs($def); - } else { - $type = $def->type; - $allow_null = isset($def->allow_null); - } - if (!isset($this->fields[$type])) $type = 0; // default - $type_obj = $this->fields[$type]; - if ($allow_null) { - $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); - } - $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); + $def = $this->config->def->info["$ns.$directive"]; + if (is_int($def)) { + $allow_null = $def < 0; + $type = abs($def); + } else { + $type = $def->type; + $allow_null = isset($def->allow_null); + } + if (!isset($this->fields[$type])) { + $type = 0; + } // default + $type_obj = $this->fields[$type]; + if ($allow_null) { + $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); + } + $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); $ret .= $this->end('td'); $ret .= $this->end('tr'); } @@ -185,19 +207,33 @@ protected function renderNamespace($ns, $directives) { /** * Printer decorator for directives that accept null */ -class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer { +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ /** * Printer being decorated + * @type HTMLPurifier_Printer */ protected $obj; + /** - * @param $obj Printer to decorate + * @param HTMLPurifier_Printer $obj Printer to decorate */ - public function __construct($obj) { + public function __construct($obj) + { parent::__construct(); $this->obj = $obj; } - public function render($ns, $directive, $value, $name, $config) { + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -215,15 +251,19 @@ public function render($ns, $directive, $value, $name, $config) { 'type' => 'checkbox', 'value' => '1', 'class' => 'null-toggle', - 'name' => "$name"."[Null_$ns.$directive]", + 'name' => "$name" . "[Null_$ns.$directive]", 'id' => "$name:Null_$ns.$directive", 'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!! ); if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) { // modify inline javascript slightly - $attr['onclick'] = "toggleWriteability('$name:Yes_$ns.$directive',checked);toggleWriteability('$name:No_$ns.$directive',checked)"; + $attr['onclick'] = + "toggleWriteability('$name:Yes_$ns.$directive',checked);" . + "toggleWriteability('$name:No_$ns.$directive',checked)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; } - if ($value === null) $attr['checked'] = 'checked'; $ret .= $this->elementEmpty('input', $attr); $ret .= $this->text(' or '); $ret .= $this->elementEmpty('br'); @@ -235,10 +275,28 @@ public function render($ns, $directive, $value, $name, $config) { /** * Swiss-army knife configuration form field printer */ -class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer { +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ public $cols = 18; + + /** + * @type int + */ public $rows = 5; - public function render($ns, $directive, $value, $name, $config) { + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -262,6 +320,7 @@ public function render($ns, $directive, $value, $name, $config) { foreach ($array as $val => $b) { $value[] = $val; } + //TODO does this need a break? case HTMLPurifier_VarParser::ALIST: $value = implode(PHP_EOL, $value); break; @@ -281,25 +340,27 @@ public function render($ns, $directive, $value, $name, $config) { $value = serialize($value); } $attr = array( - 'name' => "$name"."[$ns.$directive]", + 'name' => "$name" . "[$ns.$directive]", 'id' => "$name:$ns.$directive" ); - if ($value === null) $attr['disabled'] = 'disabled'; + if ($value === null) { + $attr['disabled'] = 'disabled'; + } if (isset($def->allowed)) { $ret .= $this->start('select', $attr); foreach ($def->allowed as $val => $b) { $attr = array(); - if ($value == $val) $attr['selected'] = 'selected'; + if ($value == $val) { + $attr['selected'] = 'selected'; + } $ret .= $this->element('option', $val, $attr); } $ret .= $this->end('select'); - } elseif ( - $type === HTMLPurifier_VarParser::TEXT || - $type === HTMLPurifier_VarParser::ITEXT || - $type === HTMLPurifier_VarParser::ALIST || - $type === HTMLPurifier_VarParser::HASH || - $type === HTMLPurifier_VarParser::LOOKUP - ) { + } elseif ($type === HTMLPurifier_VarParser::TEXT || + $type === HTMLPurifier_VarParser::ITEXT || + $type === HTMLPurifier_VarParser::ALIST || + $type === HTMLPurifier_VarParser::HASH || + $type === HTMLPurifier_VarParser::LOOKUP) { $attr['cols'] = $this->cols; $attr['rows'] = $this->rows; $ret .= $this->start('textarea', $attr); @@ -317,8 +378,18 @@ public function render($ns, $directive, $value, $name, $config) { /** * Bool form field printer */ -class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer { - public function render($ns, $directive, $value, $name, $config) { +class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer +{ + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -336,12 +407,16 @@ public function render($ns, $directive, $value, $name, $config) { $attr = array( 'type' => 'radio', - 'name' => "$name"."[$ns.$directive]", + 'name' => "$name" . "[$ns.$directive]", 'id' => "$name:Yes_$ns.$directive", 'value' => '1' ); - if ($value === true) $attr['checked'] = 'checked'; - if ($value === null) $attr['disabled'] = 'disabled'; + if ($value === true) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } $ret .= $this->elementEmpty('input', $attr); $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive")); @@ -351,12 +426,16 @@ public function render($ns, $directive, $value, $name, $config) { $attr = array( 'type' => 'radio', - 'name' => "$name"."[$ns.$directive]", + 'name' => "$name" . "[$ns.$directive]", 'id' => "$name:No_$ns.$directive", 'value' => '0' ); - if ($value === false) $attr['checked'] = 'checked'; - if ($value === null) $attr['disabled'] = 'disabled'; + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } $ret .= $this->elementEmpty('input', $attr); $ret .= $this->end('div'); diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php index e17ddd51ff..679d19ba3a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php @@ -4,11 +4,16 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer { /** - * Instance of HTMLPurifier_HTMLDefinition, for easy access + * @type HTMLPurifier_HTMLDefinition, for easy access */ protected $def; - public function render($config) { + /** + * @param HTMLPurifier_Config $config + * @return string + */ + public function render($config) + { $ret = ''; $this->config =& $config; @@ -28,8 +33,10 @@ public function render($config) { /** * Renders the Doctype table + * @return string */ - protected function renderDoctype() { + protected function renderDoctype() + { $doctype = $this->def->doctype; $ret = ''; $ret .= $this->start('table'); @@ -45,8 +52,10 @@ protected function renderDoctype() { /** * Renders environment table, which is miscellaneous info + * @return string */ - protected function renderEnvironment() { + protected function renderEnvironment() + { $def = $this->def; $ret = ''; @@ -59,28 +68,28 @@ protected function renderEnvironment() { $ret .= $this->row('Block wrap name', $def->info_block_wrapper); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Global attributes'); - $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr),0,0); + $ret .= $this->element('th', 'Global attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Tag transforms'); - $list = array(); - foreach ($def->info_tag_transform as $old => $new) { - $new = $this->getClass($new, 'TagTransform_'); - $list[] = "<$old> with $new"; - } - $ret .= $this->element('td', $this->listify($list)); + $ret .= $this->element('th', 'Tag transforms'); + $list = array(); + foreach ($def->info_tag_transform as $old => $new) { + $new = $this->getClass($new, 'TagTransform_'); + $list[] = "<$old> with $new"; + } + $ret .= $this->element('td', $this->listify($list)); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Pre-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Post-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); $ret .= $this->end('tr'); $ret .= $this->end('table'); @@ -89,8 +98,10 @@ protected function renderEnvironment() { /** * Renders the Content Sets table + * @return string */ - protected function renderContentSets() { + protected function renderContentSets() + { $ret = ''; $ret .= $this->start('table'); $ret .= $this->element('caption', 'Content Sets'); @@ -106,8 +117,10 @@ protected function renderContentSets() { /** * Renders the Elements ($info) table + * @return string */ - protected function renderInfo() { + protected function renderInfo() + { $ret = ''; $ret .= $this->start('table'); $ret .= $this->element('caption', 'Elements ($info)'); @@ -118,39 +131,39 @@ protected function renderInfo() { $ret .= $this->end('tr'); foreach ($this->def->info as $name => $def) { $ret .= $this->start('tr'); - $ret .= $this->element('th', "<$name>", array('class'=>'heavy', 'colspan' => 2)); + $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2)); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Inline content'); - $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); + $ret .= $this->element('th', 'Inline content'); + $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); $ret .= $this->end('tr'); if (!empty($def->excludes)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Excludes'); - $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); + $ret .= $this->element('th', 'Excludes'); + $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); $ret .= $this->end('tr'); } if (!empty($def->attr_transform_pre)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Pre-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); $ret .= $this->end('tr'); } if (!empty($def->attr_transform_post)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Post-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); $ret .= $this->end('tr'); } if (!empty($def->auto_close)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Auto closed by'); - $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); + $ret .= $this->element('th', 'Auto closed by'); + $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); $ret .= $this->end('tr'); } $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Allowed attributes'); - $ret .= $this->element('td',$this->listifyAttr($def->attr), array(), 0); + $ret .= $this->element('th', 'Allowed attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0); $ret .= $this->end('tr'); if (!empty($def->required_attr)) { @@ -165,64 +178,94 @@ protected function renderInfo() { /** * Renders a row describing the allowed children of an element - * @param $def HTMLPurifier_ChildDef of pertinent element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string */ - protected function renderChildren($def) { + protected function renderChildren($def) + { $context = new HTMLPurifier_Context(); $ret = ''; $ret .= $this->start('tr'); - $elements = array(); - $attr = array(); - if (isset($def->elements)) { - if ($def->type == 'strictblockquote') { - $def->validateChildren(array(), $this->config, $context); - } - $elements = $def->elements; + $elements = array(); + $attr = array(); + if (isset($def->elements)) { + if ($def->type == 'strictblockquote') { + $def->validateChildren(array(), $this->config, $context); } - if ($def->type == 'chameleon') { - $attr['rowspan'] = 2; - } elseif ($def->type == 'empty') { - $elements = array(); - } elseif ($def->type == 'table') { - $elements = array_flip(array('col', 'caption', 'colgroup', 'thead', - 'tfoot', 'tbody', 'tr')); - } - $ret .= $this->element('th', 'Allowed children', $attr); - - if ($def->type == 'chameleon') { - - $ret .= $this->element('td', - 'Block: ' . - $this->escape($this->listifyTagLookup($def->block->elements)),0,0); - $ret .= $this->end('tr'); - $ret .= $this->start('tr'); - $ret .= $this->element('td', - 'Inline: ' . - $this->escape($this->listifyTagLookup($def->inline->elements)),0,0); - - } elseif ($def->type == 'custom') { + $elements = $def->elements; + } + if ($def->type == 'chameleon') { + $attr['rowspan'] = 2; + } elseif ($def->type == 'empty') { + $elements = array(); + } elseif ($def->type == 'table') { + $elements = array_flip( + array( + 'col', + 'caption', + 'colgroup', + 'thead', + 'tfoot', + 'tbody', + 'tr' + ) + ); + } + $ret .= $this->element('th', 'Allowed children', $attr); - $ret .= $this->element('td', ''.ucfirst($def->type).': ' . - $def->dtd_regex); + if ($def->type == 'chameleon') { - } else { - $ret .= $this->element('td', - ''.ucfirst($def->type).': ' . - $this->escape($this->listifyTagLookup($elements)),0,0); - } + $ret .= $this->element( + 'td', + 'Block: ' . + $this->escape($this->listifyTagLookup($def->block->elements)), + null, + 0 + ); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element( + 'td', + 'Inline: ' . + $this->escape($this->listifyTagLookup($def->inline->elements)), + null, + 0 + ); + + } elseif ($def->type == 'custom') { + + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $def->dtd_regex + ); + + } else { + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $this->escape($this->listifyTagLookup($elements)), + null, + 0 + ); + } $ret .= $this->end('tr'); return $ret; } /** * Listifies a tag lookup table. - * @param $array Tag lookup array in form of array('tagname' => true) + * @param array $array Tag lookup array in form of array('tagname' => true) + * @return string */ - protected function listifyTagLookup($array) { + protected function listifyTagLookup($array) + { ksort($array); $list = array(); foreach ($array as $name => $discard) { - if ($name !== '#PCDATA' && !isset($this->def->info[$name])) continue; + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } $list[] = $name; } return $this->listify($list); @@ -230,13 +273,15 @@ protected function listifyTagLookup($array) { /** * Listifies a list of objects by retrieving class names and internal state - * @param $array List of objects + * @param array $array List of objects + * @return string * @todo Also add information about internal state */ - protected function listifyObjectList($array) { + protected function listifyObjectList($array) + { ksort($array); $list = array(); - foreach ($array as $discard => $obj) { + foreach ($array as $obj) { $list[] = $this->getClass($obj, 'AttrTransform_'); } return $this->listify($list); @@ -244,13 +289,17 @@ protected function listifyObjectList($array) { /** * Listifies a hash of attributes to AttrDef classes - * @param $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string */ - protected function listifyAttr($array) { + protected function listifyAttr($array) + { ksort($array); $list = array(); foreach ($array as $name => $obj) { - if ($obj === false) continue; + if ($obj === false) { + continue; + } $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; } return $this->listify($list); @@ -258,15 +307,18 @@ protected function listifyAttr($array) { /** * Creates a heavy header row + * @param string $text + * @param int $num + * @return string */ - protected function heavyHeader($text, $num = 1) { + protected function heavyHeader($text, $num = 1) + { $ret = ''; $ret .= $this->start('tr'); $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy')); $ret .= $this->end('tr'); return $ret; } - } // vim: et sw=4 sts=4 diff --git a/framework/views/da/error.php b/framework/views/da/error.php new file mode 100644 index 0000000000..86cc7046d2 --- /dev/null +++ b/framework/views/da/error.php @@ -0,0 +1,37 @@ + + + + +Fejl <?php echo $data['code']; ?> + + + + + +

        Fejl

        +

        +

        +Ovenstående fejl opstod da webserveren behandlede din forespørgsel. +

        +

        +Hvis du tror at dette er en server-fejl, kontakt venligst . +

        +

        +Tak! +

        +
        + +
        + + \ No newline at end of file diff --git a/framework/views/da/error400.php b/framework/views/da/error400.php new file mode 100644 index 0000000000..fa5b05170d --- /dev/null +++ b/framework/views/da/error400.php @@ -0,0 +1,33 @@ + + + + +Ugyldig forespørgsel + + + +

        Ugyldig forespørgsel

        +

        +

        +Serveren forstod ikke syntaksen i forespørgslen. Gentag venligst ikke +forespørgslen uændret. +

        +

        +Hvis du tror at dette er en server-fejl, kontakt venligst . +

        +
        + +
        + + \ No newline at end of file diff --git a/framework/views/da/error403.php b/framework/views/da/error403.php new file mode 100644 index 0000000000..31cde6a59a --- /dev/null +++ b/framework/views/da/error403.php @@ -0,0 +1,32 @@ + + + + +Uautoriseret + + + +

        Uautoriseret

        +

        +

        +Du har ikke tilstrækkelige rettigheder til at tilgå denne side. +

        +

        +Hvis du tror at dette er en server-fejl, kontakt venligst . +

        +
        + +
        + + \ No newline at end of file diff --git a/framework/views/da/error404.php b/framework/views/da/error404.php new file mode 100644 index 0000000000..6b79b984e0 --- /dev/null +++ b/framework/views/da/error404.php @@ -0,0 +1,33 @@ + + + + +Siden findes ikke + + + +

        Siden findes ikke

        +

        +

        +Serveren kunne ikke finde den forespurgte side. +Hvis du skrev URL'en manuelt tjek venligst om den er korrekt og prøv igen. +

        +

        +Hvis du tror at dette er en server-fejl, kontakt venligst . +

        +
        + +
        + + \ No newline at end of file diff --git a/framework/views/da/error500.php b/framework/views/da/error500.php new file mode 100644 index 0000000000..6b100b926f --- /dev/null +++ b/framework/views/da/error500.php @@ -0,0 +1,35 @@ + + + + +Intern serverfejl + + + + + +

        Intern serverfejl

        +

        +

        +Det opstod en fejl, og serveren kan ikke fuldføre forespørgslen. +Kontakt venligst for at rapportere problemet. +

        +

        +Tak! +

        +
        + +
        + + \ No newline at end of file diff --git a/framework/views/da/error503.php b/framework/views/da/error503.php new file mode 100644 index 0000000000..a3e8e4e277 --- /dev/null +++ b/framework/views/da/error503.php @@ -0,0 +1,31 @@ + + + + +Tjenesten er utilgængelig + + + +

        Tjenesten er utilgængelig

        +

        +Serveren er utilgængelig i øjeblikket grundet vedligeholdelse. Prøv venligst igen senere. +

        +

        +Tak! +

        +
        + +
        + + \ No newline at end of file diff --git a/framework/views/da/exception.php b/framework/views/da/exception.php new file mode 100644 index 0000000000..c6e56b316e --- /dev/null +++ b/framework/views/da/exception.php @@ -0,0 +1,242 @@ + + + + +<?php echo $data['type']; ?> + + + + + +
        +

        + +

        + charset))?> +

        + +
        +

        charset)."({$data['line']})"?>

        + renderSourceCode($data['file'],$data['line'],$this->maxSourceLines); ?> +
        + +
        +

        Stack Trace

        + + + $trace): ?> + isCoreCode($trace)) + $cssClass='core collapsed'; + elseif(++$count>3) + $cssClass='app collapsed'; + else + $cssClass='app expanded'; + $hasCode=$trace['file']!=='unknown' && is_file($trace['file']); + ?> + + + + + +
        + # + +
        + +
        +
        +
        + + charset)."(".$trace['line'].")"; + echo ': '; + if(!empty($trace['class'])) + echo "{$trace['class']}{$trace['type']}"; + echo "{$trace['function']}("; + if(!empty($trace['args'])) + echo htmlspecialchars($this->argumentsToString($trace['args']),ENT_QUOTES,Yii::app()->charset); + echo ')'; + ?> +
        + + renderSourceCode($trace['file'],$trace['line'],$this->maxTraceSourceLines); ?> +
        +
        + +
        + +
        +
        + + + + + diff --git a/framework/views/da/log-firebug.php b/framework/views/da/log-firebug.php new file mode 100644 index 0000000000..61d80835cc --- /dev/null +++ b/framework/views/da/log-firebug.php @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/framework/views/da/log.php b/framework/views/da/log.php new file mode 100644 index 0000000000..58da29e37b --- /dev/null +++ b/framework/views/da/log.php @@ -0,0 +1,40 @@ + + + + + + + + + + + +'#DFFFE0', + CLogger::LEVEL_INFO=>'#FFFFDF', + CLogger::LEVEL_WARNING=>'#FFDFE5', + CLogger::LEVEL_ERROR=>'#FFC0CB', +); +foreach($data as $index=>$log) +{ + $color=($index%2)?'#F5F5F5':'#FFFFFF'; + if(isset($colors[$log[1]])) + $color=$colors[$log[1]]; + $message='
        '.CHtml::encode(wordwrap($log[0])).'
        '; + $time=date('H:i:s.',$log[3]).(int)(($log[3]-(int)$log[3])*1000000); + + echo << + + + + + +EOD; +} +?> +
        + Applikationslog +
        TidNiveauKategoriBesked
        {$time}{$log[1]}{$log[2]}{$message}
        + \ No newline at end of file diff --git a/framework/views/da/profile-callstack-firebug.php b/framework/views/da/profile-callstack-firebug.php new file mode 100644 index 0000000000..890eadf575 --- /dev/null +++ b/framework/views/da/profile-callstack-firebug.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/framework/views/da/profile-callstack.php b/framework/views/da/profile-callstack.php new file mode 100644 index 0000000000..a0ee300de4 --- /dev/null +++ b/framework/views/da/profile-callstack.php @@ -0,0 +1,30 @@ + + + + + + + + + +$entry) +{ + $color=($index%2)?'#F5F5F5':'#FFFFFF'; + list($proc,$time,$level)=$entry; + $proc=CHtml::encode($proc); + $time=sprintf('%0.5f',$time); + $spaces=str_repeat(' ',$level*8); + + echo << + + + +EOD; +} +?> +
        + Profileringsrapport +
        FunktionTid (sek)
        {$spaces}{$proc}{$time}
        + \ No newline at end of file diff --git a/framework/views/da/profile-summary-firebug.php b/framework/views/da/profile-summary-firebug.php new file mode 100644 index 0000000000..135cfa3805 --- /dev/null +++ b/framework/views/da/profile-summary-firebug.php @@ -0,0 +1,22 @@ + diff --git a/framework/views/da/profile-summary.php b/framework/views/da/profile-summary.php new file mode 100644 index 0000000000..9a92a152df --- /dev/null +++ b/framework/views/da/profile-summary.php @@ -0,0 +1,41 @@ + + + + + + + + + + + + + +$entry) +{ + $color=($index%2)?'#F5F5F5':'#FFFFFF'; + $proc=CHtml::encode($entry[0]); + $min=sprintf('%0.5f',$entry[2]); + $max=sprintf('%0.5f',$entry[3]); + $total=sprintf('%0.5f',$entry[4]); + $average=sprintf('%0.5f',$entry[4]/$entry[1]); + + echo << + + + + + + + +EOD; +} +?> +
        + Resultat af profilering + (Tid: getExecutionTime()); ?>s, + Hukommelsesbrug: getMemoryUsage()/1024); ?>KB) +
        FunktionAntalTotalt (sek)Gennemsnit (sek)Min (sek)Maks (sek)
        {$proc}{$entry[1]}{$total}{$average}{$min}{$max}
        + \ No newline at end of file diff --git a/framework/web/CAssetManager.php b/framework/web/CAssetManager.php index 38708d455a..d7e7963eed 100644 --- a/framework/web/CAssetManager.php +++ b/framework/web/CAssetManager.php @@ -211,7 +211,7 @@ public function publish($path,$hashByName=false,$level=-1,$forceCopy=null) throw new CException(Yii::t('yii','The "forceCopy" and "linkAssets" cannot be both true.')); if(isset($this->_published[$path])) return $this->_published[$path]; - elseif(($src=realpath($path))!==false) + elseif(is_string($path) && ($src=realpath($path))!==false) { $dir=$this->generatePath($src,$hashByName); $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir; @@ -271,7 +271,7 @@ public function publish($path,$hashByName=false,$level=-1,$forceCopy=null) */ public function getPublishedPath($path,$hashByName=false) { - if(($path=realpath($path))!==false) + if(is_string($path) && ($path=realpath($path))!==false) { $base=$this->getBasePath().DIRECTORY_SEPARATOR.$this->generatePath($path,$hashByName); return is_file($path) ? $base.DIRECTORY_SEPARATOR.basename($path) : $base ; @@ -295,7 +295,7 @@ public function getPublishedUrl($path,$hashByName=false) { if(isset($this->_published[$path])) return $this->_published[$path]; - if(($path=realpath($path))!==false) + if(is_string($path) && ($path=realpath($path))!==false) { $base=$this->getBaseUrl().'/'.$this->generatePath($path,$hashByName); return is_file($path) ? $base.'/'.basename($path) : $base; @@ -325,9 +325,9 @@ protected function hash($path) protected function generatePath($file,$hashByName=false) { if (is_file($file)) - $pathForHashing=$hashByName ? basename($file) : dirname($file).filemtime($file); + $pathForHashing=$hashByName ? dirname($file) : dirname($file).filemtime($file); else - $pathForHashing=$hashByName ? basename($file) : $file.filemtime($file); + $pathForHashing=$hashByName ? $file : $file.filemtime($file); return $this->hash($pathForHashing); } diff --git a/framework/web/CBaseController.php b/framework/web/CBaseController.php index 9f5d649f7a..e738f17656 100644 --- a/framework/web/CBaseController.php +++ b/framework/web/CBaseController.php @@ -163,8 +163,16 @@ public function widget($className,$properties=array(),$captureOutput=false) { ob_start(); ob_implicit_flush(false); - $widget=$this->createWidget($className,$properties); - $widget->run(); + try + { + $widget=$this->createWidget($className,$properties); + $widget->run(); + } + catch(Exception $e) + { + ob_end_clean(); + throw $e; + } return ob_get_clean(); } else diff --git a/framework/web/CClientScript.php b/framework/web/CClientScript.php index f7d3c20ccc..bddaa9160a 100644 --- a/framework/web/CClientScript.php +++ b/framework/web/CClientScript.php @@ -318,13 +318,14 @@ protected function renderScriptBatch(array $scripts) $scriptContent = $scriptValue['content']; unset($scriptValue['content']); $scriptHtmlOptions = $scriptValue; + ksort($scriptHtmlOptions); } else { $scriptContent = $scriptValue; $scriptHtmlOptions = array(); } - $key=serialize(ksort($scriptHtmlOptions)); + $key=serialize($scriptHtmlOptions); $scriptBatches[$key]['htmlOptions']=$scriptHtmlOptions; $scriptBatches[$key]['scripts'][]=$scriptContent; } @@ -553,7 +554,7 @@ public function getPackageBaseUrl($name) * Registers a script package that is listed in {@link packages}. * This method is the same as {@link registerCoreScript}. * @param string $name the name of the script package. - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). * @since 1.1.7 * @see renderCoreScript */ @@ -565,7 +566,7 @@ public function registerPackage($name) /** * Registers a script package that is listed in {@link packages}. * @param string $name the name of the script package. - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). * @see renderCoreScript */ public function registerCoreScript($name) @@ -600,7 +601,7 @@ public function registerCoreScript($name) * Registers a CSS file * @param string $url URL of the CSS file * @param string $media media that the CSS file should be applied to. If empty, it means all media types. - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). */ public function registerCssFile($url,$media='') { @@ -616,7 +617,7 @@ public function registerCssFile($url,$media='') * @param string $id ID that uniquely identifies this piece of CSS code * @param string $css the CSS code * @param string $media media that the CSS code should be applied to. If empty, it means all media types. - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). */ public function registerCss($id,$css,$media='') { @@ -637,7 +638,7 @@ public function registerCss($id,$css,$media='') *
      • CClientScript::POS_END : the script is inserted at the end of the body section.
      • *
      * @param array $htmlOptions additional HTML attributes - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). */ public function registerScriptFile($url,$position=null,array $htmlOptions=array()) { @@ -671,7 +672,7 @@ public function registerScriptFile($url,$position=null,array $htmlOptions=array( *
    * @param array $htmlOptions additional HTML attributes * Note: HTML attributes are not allowed for script positions "CClientScript::POS_LOAD" and "CClientScript::POS_READY". - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). */ public function registerScript($id,$script,$position=null,array $htmlOptions=array()) { @@ -711,7 +712,7 @@ public function registerScript($id,$script,$position=null,array $htmlOptions=arr * @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated * @param array $options other options in name-value pairs (e.g. 'scheme', 'lang') * @param string $id Optional id of the meta tag to avoid duplicates - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). */ public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=array(),$id=null) { @@ -734,7 +735,7 @@ public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=arr * @param string $href href attribute of the link tag. If null, the attribute will not be generated. * @param string $media media attribute of the link tag. If null, the attribute will not be generated. * @param array $options other options in name-value pairs - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5). */ public function registerLinkTag($relation=null,$type=null,$href=null,$media=null,$options=array()) { @@ -829,7 +830,7 @@ protected function recordCachingAction($context,$method,$params) * @param string $name the name of the script package. * @param array $definition the definition array of the script package, * @see CClientScript::packages. - * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.10). + * @return static the CClientScript object itself (to support method chaining, available since version 1.1.10). * * @since 1.1.9 */ diff --git a/framework/web/CHttpRequest.php b/framework/web/CHttpRequest.php index 36538bbbef..4b30a1c7e6 100644 --- a/framework/web/CHttpRequest.php +++ b/framework/web/CHttpRequest.php @@ -32,10 +32,11 @@ * @property string $requestUri The request URI portion for the currently requested URL. * @property string $queryString Part of the request URL that is after the question mark. * @property boolean $isSecureConnection If the request is sent via secure channel (https). - * @property string $requestType Request type, such as GET, POST, HEAD, PUT, DELETE. + * @property string $requestType Request type, such as GET, POST, HEAD, PUT, PATCH, DELETE. * @property boolean $isPostRequest Whether this is a POST request. * @property boolean $isDeleteRequest Whether this is a DELETE request. * @property boolean $isPutRequest Whether this is a PUT request. + * @property boolean $isPatchRequest Whether this is a PATCH request. * @property boolean $isAjaxRequest Whether this is an AJAX (XMLHttpRequest) request. * @property boolean $isFlashRequest Whether this is an Adobe Flash or Adobe Flex request. * @property string $serverName Server name. @@ -250,7 +251,32 @@ public function getPut($name,$defaultValue=null) } /** - * Returns request parameters. Typically PUT or DELETE. + * Returns the named PATCH parameter value. + * If the PATCH parameter does not exist or if the current request is not a PATCH request, + * the second parameter to this method will be returned. + * If the PATCH request was tunneled through POST via _method parameter, the POST parameter + * will be returned instead. + * @param string $name the PATCH parameter name + * @param mixed $defaultValue the default parameter value if the PATCH parameter does not exist. + * @return mixed the PATCH parameter value + * @since 1.1.15 + */ + public function getPatch($name,$defaultValue=null) + { + if($this->getIsPatchViaPostRequest()) + return $this->getPost($name, $defaultValue); + + if($this->getIsPatchRequest()) + { + $restParams=$this->getRestParams(); + return isset($restParams[$name]) ? $restParams[$name] : $defaultValue; + } + else + return $defaultValue; + } + + /** + * Returns request parameters. Typically PUT, PATCH or DELETE. * @return array the request parameters * @since 1.1.7 * @since 1.1.13 method became public @@ -446,7 +472,15 @@ public function getPathInfo() else throw new CException(Yii::t('yii','CHttpRequest is unable to determine the path info of the request.')); - $this->_pathInfo=trim($pathInfo,'/'); + if($pathInfo==='/') + $pathInfo=''; + elseif($pathInfo[0]==='/') + $pathInfo=substr($pathInfo,1); + + if(($posEnd=strlen($pathInfo)-1)>0 && $pathInfo[$posEnd]==='/') + $pathInfo=substr($pathInfo,0,$posEnd); + + $this->_pathInfo=$pathInfo; } return $this->_pathInfo; } @@ -537,16 +571,16 @@ public function getQueryString() */ public function getIsSecureConnection() { - return isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']=='on' || $_SERVER['HTTPS']==1) - || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']=='https'; + return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'],'on')===0 || $_SERVER['HTTPS']==1) + || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'],'https')===0; } /** - * Returns the request type, such as GET, POST, HEAD, PUT, DELETE. + * Returns the request type, such as GET, POST, HEAD, PUT, PATCH, DELETE. * Request type can be manually set in POST requests with a parameter named _method. Useful - * for RESTful request from older browsers which do not support PUT or DELETE + * for RESTful request from older browsers which do not support PUT, PATCH or DELETE * natively (available since version 1.1.11). - * @return string request type, such as GET, POST, HEAD, PUT, DELETE. + * @return string request type, such as GET, POST, HEAD, PUT, PATCH, DELETE. */ public function getRequestType() { @@ -605,6 +639,26 @@ protected function getIsPutViaPostRequest() return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PUT'); } + /** + * Returns whether this is a PATCH request. + * @return boolean whether this is a PATCH request. + * @since 1.1.15 + */ + public function getIsPatchRequest() + { + return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'PATCH')) || $this->getIsPatchViaPostRequest(); + } + + /** + * Returns whether this is a PATCH request which was tunneled through POST. + * @return boolean whether this is a PATCH request tunneled through POST. + * @since 1.1.15 + */ + protected function getIsPatchViaPostRequest() + { + return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PATCH'); + } + /** * Returns whether this is an AJAX (XMLHttpRequest) request. * @return boolean whether this is an AJAX (XMLHttpRequest) request. @@ -1062,7 +1116,7 @@ public function sendFile($fileName,$content,$mimeType=null,$terminate=true) header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header("Content-type: $mimeType"); + header("Content-Type: $mimeType"); header('Content-Length: '.$length); header("Content-Disposition: attachment; filename=\"$fileName\""); header('Content-Transfer-Encoding: binary'); @@ -1159,7 +1213,7 @@ public function xSendFile($filePath, $options=array()) $options['xHeader']='X-Sendfile'; if($options['mimeType']!==null) - header('Content-type: '.$options['mimeType']); + header('Content-Type: '.$options['mimeType']); header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"'); if(isset($options['addHeaders'])) { @@ -1225,6 +1279,7 @@ public function validateCsrfToken($event) { if ($this->getIsPostRequest() || $this->getIsPutRequest() || + $this->getIsPatchRequest() || $this->getIsDeleteRequest()) { $cookies=$this->getCookies(); @@ -1238,6 +1293,9 @@ public function validateCsrfToken($event) case 'PUT': $userToken=$this->getPut($this->csrfTokenName); break; + case 'PATCH': + $userToken=$this->getPatch($this->csrfTokenName); + break; case 'DELETE': $userToken=$this->getDelete($this->csrfTokenName); } diff --git a/framework/web/CSort.php b/framework/web/CSort.php index 9fb5fdb103..5353a71d50 100644 --- a/framework/web/CSort.php +++ b/framework/web/CSort.php @@ -245,9 +245,9 @@ public function getOrderBy($criteria=null) if(is_array($definition)) { if($descending) - $orders[]=isset($definition['desc']) ? $definition['desc'] : $attribute.' DESC'; + $orders[]=isset($definition['desc']) ? (is_array($definition['desc']) ? implode(', ',$definition['desc']) : $definition['desc']) : $attribute.' DESC'; else - $orders[]=isset($definition['asc']) ? $definition['asc'] : $attribute; + $orders[]=isset($definition['asc']) ? (is_array($definition['asc']) ? implode(', ',$definition['asc']) : $definition['asc']) : $attribute; } elseif($definition!==false) { @@ -469,4 +469,4 @@ protected function createLink($attribute,$label,$url,$htmlOptions) { return CHtml::link($label,$url,$htmlOptions); } -} \ No newline at end of file +} diff --git a/framework/web/CUrlManager.php b/framework/web/CUrlManager.php index cca699f41b..688a3ab0dd 100644 --- a/framework/web/CUrlManager.php +++ b/framework/web/CUrlManager.php @@ -658,6 +658,7 @@ public function __construct($route,$pattern) $this->route=trim($route,'/'); $tr2['/']=$tr['/']='\\/'; + $tr['.']='\\.'; if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2)) { diff --git a/framework/web/CWebApplication.php b/framework/web/CWebApplication.php index fa651d4347..8d63a12b70 100644 --- a/framework/web/CWebApplication.php +++ b/framework/web/CWebApplication.php @@ -346,7 +346,7 @@ public function createController($route,$owner=null) $classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php'; if($owner->controllerNamespace!==null) - $className=$owner->controllerNamespace.'\\'.$className; + $className=$owner->controllerNamespace.'\\'.str_replace('/','\\',$controllerID).$className; if(is_file($classFile)) { diff --git a/framework/web/actions/CViewAction.php b/framework/web/actions/CViewAction.php index 22886f8e99..daa3c8609e 100644 --- a/framework/web/actions/CViewAction.php +++ b/framework/web/actions/CViewAction.php @@ -93,7 +93,7 @@ public function getRequestedView() * Resolves the user-specified view into a valid view name. * @param string $viewPath user-specified view in the format of 'path.to.view'. * @return string fully resolved view in the format of 'path/to/view'. - * @throw CHttpException if the user-specified view is invalid + * @throws CHttpException if the user-specified view is invalid */ protected function resolveView($viewPath) { diff --git a/framework/web/form/CFormElement.php b/framework/web/form/CFormElement.php index a8a24280df..841a2c101e 100644 --- a/framework/web/form/CFormElement.php +++ b/framework/web/form/CFormElement.php @@ -86,6 +86,27 @@ public function __get($name) array('{class}'=>get_class($this), '{property}'=>$name))); } + /** + * Checks a property value or an attribute value on existence or not null + * Do not call this method. This is a PHP magic method that we override + * to allow using the following syntax to read a property or attribute: + *
    +	 * isset($element->propertyName);
    +	 * 
    + * @param string $name the property or attribute name + * @return boolean + */ + public function __isset($name) + { + $getter='get'.$name; + if(method_exists($this,$getter)) + return $this->$getter()!==null; + elseif(isset($this->attributes[$name])) + return isset($this->attributes[$name]); + else + return false; + } + /** * Sets value of a property or attribute. * Do not call this method. This is a PHP magic method that we override diff --git a/framework/web/form/CFormInputElement.php b/framework/web/form/CFormInputElement.php index 0549dcbbc1..3eb7390918 100644 --- a/framework/web/form/CFormInputElement.php +++ b/framework/web/form/CFormInputElement.php @@ -29,6 +29,13 @@ *
  17. number: an HTML5 number input generated using {@link CHtml::activeNumberField}
  18. *
  19. range: an HTML5 range input generated using {@link CHtml::activeRangeField}
  20. *
  21. date: an HTML5 date input generated using {@link CHtml::activeDateField}
  22. + *
  23. time: an HTML5 time input generated using {@link CHtml::activeTimeField}
  24. + *
  25. datetime: an HTML5 datetime input generated using {@link CHtml::activeDateTimeField}
  26. + *
  27. datetimelocal: an HTML5 datetime-local input generated using {@link CHtml::activeDateTimeLocalField}
  28. + *
  29. week: an HTML5 week input generated using {@link CHtml::activeWeekField}
  30. + *
  31. color: an HTML5 color input generated using {@link CHtml::activeColorField}
  32. + *
  33. tel: an HTML5 tel input generated using {@link CHtml::activeTelField}
  34. + *
  35. search: an HTML5 search input generated using {@link CHtml::activeSearchField}
  36. * * The {@link type} property can also be a class name or a path alias to the class. In this case, * the input is generated using a widget of the specified class. Note, the widget must @@ -68,7 +75,14 @@ class CFormInputElement extends CFormElement 'email'=>'activeEmailField', 'number'=>'activeNumberField', 'range'=>'activeRangeField', - 'date'=>'activeDateField' + 'date'=>'activeDateField', + 'time'=>'activeTimeField', + 'datetime'=>'activeDateTimeField', + 'datetimelocal'=>'activeDateTimeLocalField', + 'week'=>'activeWeekField', + 'color'=>'activeColorField', + 'tel'=>'activeTelField', + 'search'=>'activeSearchField', ); /** @@ -197,9 +211,7 @@ public function renderLabel() ); if(!empty($this->attributes['id'])) - { - $options['for'] = $this->attributes['id']; - } + $options['for']=$this->attributes['id']; return CHtml::activeLabel($this->getParent()->getModel(), $this->name, $options); } diff --git a/framework/web/helpers/CHtml.php b/framework/web/helpers/CHtml.php index 3758c5c637..f2e91ef38e 100644 --- a/framework/web/helpers/CHtml.php +++ b/framework/web/helpers/CHtml.php @@ -12,6 +12,11 @@ /** * CHtml is a static class that provides a collection of helper methods for creating HTML views. * + * Nearly all of the methods in this class allow setting additional html attributes for the html + * tags they generate. You can specify for example. 'class', 'style' or 'id' for an html element. + * For example when using array('class' => 'my-class', 'target' => '_blank') as htmlOptions + * it will result in the html attributes rendered like this: class="my-class" target="_blank". + * * @author Qiang Xue * @package system.web.helpers * @since 1.0 @@ -335,6 +340,14 @@ public static function form($action='',$method='post',$htmlOptions=array()) public static function beginForm($action='',$method='post',$htmlOptions=array()) { $htmlOptions['action']=$url=self::normalizeUrl($action); + if(strcasecmp($method,'get')!==0 && strcasecmp($method,'post')!==0) + { + $customMethod=$method; + $method='post'; + } + else + $customMethod=false; + $htmlOptions['method']=$method; $form=self::tag('form',$htmlOptions,false,false); $hiddens=array(); @@ -351,6 +364,8 @@ public static function beginForm($action='',$method='post',$htmlOptions=array()) $request=Yii::app()->request; if($request->enableCsrfValidation && !strcasecmp($method,'post')) $hiddens[]=self::hiddenField($request->csrfTokenName,$request->getCsrfToken(),array('id'=>false)); + if($customMethod!==false) + $hiddens[]=self::hiddenField('_method',$customMethod); if($hiddens!==array()) $form.="\n".self::tag('div',array('style'=>'display:none'),implode("\n",$hiddens)); return $form; @@ -580,6 +595,23 @@ public static function label($label,$for,$htmlOptions=array()) return self::tag('label',$htmlOptions,$label); } + /** + * Generates a color picker field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + * @since 1.1.15 + */ + public static function colorField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('color',$name,$value,$htmlOptions); + } + /** * Generates a text field input. * @param string $name the input name @@ -596,6 +628,22 @@ public static function textField($name,$value='',$htmlOptions=array()) return self::inputField('text',$name,$value,$htmlOptions); } + /** + * Generates a search field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + * @since 1.1.15 + */ + public static function searchField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('search',$name,$value,$htmlOptions); + } /** * Generates a number field input. * @param string $name the input name @@ -664,6 +712,57 @@ public static function timeField($name,$value='',$htmlOptions=array()) return self::inputField('time',$name,$value,$htmlOptions); } + /** + * Generates a datetime field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + * @since 1.1.15 + */ + public static function dateTimeField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('datetime',$name,$value,$htmlOptions); + } + + /** + * Generates a local datetime field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + * @since 1.1.15 + */ + public static function dateTimeLocalField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('datetime-local',$name,$value,$htmlOptions); + } + + /** + * Generates a week field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + * @since 1.1.15 + */ + public static function weekField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('week',$name,$value,$htmlOptions); + } + /** * Generates an email field input. * @param string $name the input name @@ -1151,7 +1250,7 @@ public static function radioButtonList($name,$select,$data,$htmlOptions=array()) { if(!is_array($htmlOptions['empty'])) $htmlOptions['empty']=array(''=>$htmlOptions['empty']); - $data=array_merge($htmlOptions['empty'],$data); + $data=CMap::mergeArray($htmlOptions['empty'],$data); unset($htmlOptions['empty']); } @@ -1574,6 +1673,86 @@ public static function activeTimeField($model,$attribute,$htmlOptions=array()) return self::activeInputField('time',$model,$attribute,$htmlOptions); } + /** + * Generates a datetime field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + * @since 1.1.15 + */ + public static function activeDateTimeField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('datetime',$model,$attribute,$htmlOptions); + } + + /** + * Generates a datetime-local field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + * @since 1.1.15 + */ + public static function activeDateTimeLocalField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('datetime-local',$model,$attribute,$htmlOptions); + } + + /** + * Generates a week field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + * @since 1.1.15 + */ + public static function activeWeekField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('week',$model,$attribute,$htmlOptions); + } + + /** + * Generates a color picker field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + * @since 1.1.15 + */ + public static function activeColorField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('color',$model,$attribute,$htmlOptions); + } + /** * Generates a telephone field input for a model attribute. * If the attribute has input error, the input field's CSS class will @@ -1891,6 +2070,12 @@ public static function activeListBox($model,$attribute,$data,$htmlOptions=array( * or is false, the 'check all' checkbox will be displayed at the beginning of * the checkbox list. *
  37. encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.
  38. + *
  39. labelOptions: array, specifies the additional HTML attributes to be rendered + * for every label tag in the list.
  40. + *
  41. container: string, specifies the checkboxes enclosing tag. Defaults to 'span'. + * If the value is an empty string, no enclosing tag will be generated
  42. + *
  43. baseID: string, specifies the base ID prefix to be used for checkboxes in the list. + * This option is available since version 1.1.13.
  44. * * Since 1.1.7, a special option named 'uncheckValue' is available. It can be used to set the value * that will be returned when the checkbox is not checked. By default, this value is ''. @@ -1936,9 +2121,21 @@ public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=a *
      *
    • template: string, specifies how each radio button is rendered. Defaults * to "{input} {label}", where "{input}" will be replaced by the generated - * radio button input tag while "{label}" will be replaced by the corresponding radio button label.
    • + * radio button input tag while "{label}" will be replaced by the corresponding radio button label, + * {beginLabel} will be replaced by <label> with labelOptions, {labelTitle} will be replaced + * by the corresponding radio button label title and {endLabel} will be replaced by </label> *
    • separator: string, specifies the string that separates the generated radio buttons. Defaults to new line (
      ).
    • *
    • encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.
    • + *
    • labelOptions: array, specifies the additional HTML attributes to be rendered + * for every label tag in the list.
    • + *
    • container: string, specifies the radio buttons enclosing tag. Defaults to 'span'. + * If the value is an empty string, no enclosing tag will be generated
    • + *
    • baseID: string, specifies the base ID prefix to be used for radio buttons in the list. + * This option is available since version 1.1.13.
    • + *
    • empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a radio button at the beginning. Note, the text label will NOT be HTML-encoded. + * This option is available since version 1.1.14.
    • *
    * Since version 1.1.7, a special option named 'uncheckValue' is available that can be used to specify the value * returned when the radio button is not checked. By default, this value is ''. Internally, a hidden field is @@ -2200,7 +2397,7 @@ public static function setModelNameConverter($converter) else throw new CException(Yii::t('yii','The $converter argument must be a valid callback or null.')); } - + /** * Generates input field name for a model attribute. * Unlike {@link resolveName}, this method does NOT modify the attribute name. @@ -2473,7 +2670,7 @@ public static function resolveNameID($model,&$attribute,&$htmlOptions) public static function resolveName($model,&$attribute) { $modelName=self::modelName($model); - + if(($pos=strpos($attribute,'['))!==false) { if($pos!==0) // e.g. name[a][b] diff --git a/framework/web/helpers/CJSON.php b/framework/web/helpers/CJSON.php index aa17b02448..1d8f3d658c 100644 --- a/framework/web/helpers/CJSON.php +++ b/framework/web/helpers/CJSON.php @@ -251,7 +251,15 @@ public static function encode($var) return '[' . join(',', array_map(array('CJSON', 'encode'), $var)) . ']'; case 'object': - if ($var instanceof Traversable) + // Check for the JsonSerializable interface available in PHP5.4 + // Note that instanceof returns false in case it doesnt know the interface. + if (interface_exists('JsonSerializable', false) && $var instanceof JsonSerializable) + { + // We use the function defined in the interface instead of json_encode. + // This way even for PHP < 5.4 one could define the interface and use it. + return self::encode($var->jsonSerialize()); + } + elseif ($var instanceof Traversable) { $vars = array(); foreach ($var as $k=>$v) diff --git a/framework/web/helpers/CJavaScript.php b/framework/web/helpers/CJavaScript.php index 9cebcf2e45..cf54dbf8a8 100644 --- a/framework/web/helpers/CJavaScript.php +++ b/framework/web/helpers/CJavaScript.php @@ -78,7 +78,7 @@ public static function encode($value,$safe=false) elseif($value===INF) return 'Number.POSITIVE_INFINITY'; else - return str_replace(',','.',(float)$var); // locale-independent representation + return str_replace(',','.',(float)$value); // locale-independent representation } elseif($value instanceof CJavaScriptExpression) return $value->__toString(); diff --git a/framework/web/services/CWsdlGenerator.php b/framework/web/services/CWsdlGenerator.php index 06c24eb7cb..ec4357761d 100644 --- a/framework/web/services/CWsdlGenerator.php +++ b/framework/web/services/CWsdlGenerator.php @@ -97,7 +97,7 @@ * * The Group indicators can be also injected via custom soap definitions as XML node into WSDL structure. * - * In the following example, class Foo will create a XML node ... with children attributes expected in pre-defined order. + * In the following example, class Foo will create a XML node <xsd:Foo><xsd:sequence> ... </xsd:sequence></xsd:Foo> with children attributes expected in pre-defined order. *
      * / *
      *   * @soap-indicator sequence
    @@ -142,7 +142,7 @@
      *     public $date_of_birth;
      * }
      * 
    - * In the example above, WSDL generator would inject under XML node the code block defined by @soap-wsdl lines. + * In the example above, WSDL generator would inject under XML node <xsd:User> the code block defined by @soap-wsdl lines. * * By inserting into SOAP URL link the parameter "?makedoc", WSDL generator will output human-friendly overview of all complex data types rather than XML WSDL file. * Each complex type is described in a separate HTML table and recognizes also the '@example' PHPDoc tag. See {@link buildHtmlDocs()}. @@ -648,7 +648,7 @@ protected function addService($dom,$serviceUrl) *
  45. Max - maximum number of occurrences
  46. *
  47. Description - Detailed description of the attribute.
  48. *
  49. Example - Attribute example value if provided via PHPDoc property @example.
  50. - *
      + *
    * * @param bool $return If true, generated HTML output will be returned rather than directly sent to output buffer */ diff --git a/framework/web/widgets/CActiveForm.php b/framework/web/widgets/CActiveForm.php index 1605fd4348..532afc520b 100644 --- a/framework/web/widgets/CActiveForm.php +++ b/framework/web/widgets/CActiveForm.php @@ -161,7 +161,7 @@ class CActiveForm extends CWidget */ public $stateful=false; /** - * @var string the CSS class name for error messages. + * @var string the CSS class name for error messages. * Since 1.1.14 this defaults to 'errorMessage' defined in {@link CHtml::$errorMessageCss}. * Individual {@link error} call may override this value by specifying the 'class' HTML option. */ @@ -331,7 +331,7 @@ public function init() echo CHtml::statefulForm($this->action, $this->method, $this->htmlOptions); else echo CHtml::beginForm($this->action, $this->method, $this->htmlOptions); - + if($this->errorMessageCssClass===null) $this->errorMessageCssClass=CHtml::$errorMessageCss; } @@ -410,9 +410,9 @@ public function run() *
      *
    • inputID
    • *
    - * When an CActiveForm input field uses a custom ID, for ajax/client validation to work properly + * When an CActiveForm input field uses a custom ID, for ajax/client validation to work properly * inputID should be set to the same ID - * + * * Example: *
     	 * 
    @@ -421,7 +421,7 @@ public function run() * error($model,'attribute',array('inputID'=>'custom-id')); ?> *
    *
    - * + * * When client-side validation is enabled, an option named "clientValidation" is also recognized. * This option should take a piece of JavaScript code to perform client-side validation. In the code, * the variables are predefined: @@ -695,9 +695,73 @@ public function timeField($model,$attribute,$htmlOptions=array()) } /** - * Renders a time field for a model attribute. - * This method is a wrapper of {@link CHtml::activeTimeField}. - * Please check {@link CHtml::activeTimeField} for detailed information + * Renders a datetime field for a model attribute. + * This method is a wrapper of {@link CHtml::activeDateTimeField}. + * Please check {@link CHtml::activeDateTimeField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + * @since 1.1.15 + */ + public function dateTimeField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeDateTimeField($model,$attribute,$htmlOptions); + } + + /** + * Renders a local datetime field for a model attribute. + * This method is a wrapper of {@link CHtml::activeDateTimeLocalField}. + * Please check {@link CHtml::activeDateTimeLocalField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + * @since 1.1.15 + */ + public function dateTimeLocalField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeDateTimeLocalField($model,$attribute,$htmlOptions); + } + + /** + * Renders a week field for a model attribute. + * This method is a wrapper of {@link CHtml::activeWeekField}. + * Please check {@link CHtml::activeWeekField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + * @since 1.1.15 + */ + public function weekField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeWeekField($model,$attribute,$htmlOptions); + } + + /** + * Renders a color picker field for a model attribute. + * This method is a wrapper of {@link CHtml::activeColorField}. + * Please check {@link CHtml::activeColorField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + * @since 1.1.15 + */ + public function colorField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeColorField($model,$attribute,$htmlOptions); + } + + /** + * Renders a tel field for a model attribute. + * This method is a wrapper of {@link CHtml::activeTelField}. + * Please check {@link CHtml::activeTelField} for detailed information * about the parameters for this method. * @param CModel $model the data model * @param string $attribute the attribute diff --git a/framework/web/widgets/CHtmlPurifier.php b/framework/web/widgets/CHtmlPurifier.php index f893c30ac4..23d33d3602 100644 --- a/framework/web/widgets/CHtmlPurifier.php +++ b/framework/web/widgets/CHtmlPurifier.php @@ -88,7 +88,7 @@ public function purify($content) /** * Set the options for HTML Purifier and create a new HTML Purifier instance based on these options. * @param mixed $options the options for HTML Purifier - * @return CHtmlPurifier + * @return static the object instance itself */ public function setOptions($options) { diff --git a/framework/web/widgets/captcha/CCaptchaAction.php b/framework/web/widgets/captcha/CCaptchaAction.php index 8b11158919..6783827de4 100644 --- a/framework/web/widgets/captcha/CCaptchaAction.php +++ b/framework/web/widgets/captcha/CCaptchaAction.php @@ -190,12 +190,12 @@ public function validate($input,$caseSensitive) */ protected function generateVerifyCode() { + if($this->minLength > $this->maxLength) + $this->maxLength = $this->minLength; if($this->minLength < 3) $this->minLength = 3; if($this->maxLength > 20) $this->maxLength = 20; - if($this->minLength > $this->maxLength) - $this->maxLength = $this->minLength; $length = mt_rand($this->minLength,$this->maxLength); $letters = 'bcdfghjklmnpqrstvwxyz'; @@ -258,7 +258,7 @@ protected function renderImageGD($code) $this->foreColor % 0x100); if($this->fontFile === null) - $this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf'; + $this->fontFile = dirname(__FILE__).DIRECTORY_SEPARATOR.'SpicyRice.ttf'; $length = strlen($code); $box = imagettfbbox(30,0,$this->fontFile,$code); @@ -282,7 +282,7 @@ protected function renderImageGD($code) header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Content-Transfer-Encoding: binary'); - header("Content-type: image/png"); + header("Content-Type: image/png"); imagepng($image); imagedestroy($image); } @@ -301,7 +301,7 @@ protected function renderImageImagick($code) $image->newImage($this->width,$this->height,$backColor); if($this->fontFile===null) - $this->fontFile=dirname(__FILE__).'/SpicyRice.ttf'; + $this->fontFile=dirname(__FILE__).DIRECTORY_SEPARATOR.'SpicyRice.ttf'; $draw=new ImagickDraw(); $draw->setFont($this->fontFile); @@ -329,7 +329,7 @@ protected function renderImageImagick($code) header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Content-Transfer-Encoding: binary'); - header("Content-type: image/png"); + header("Content-Type: image/png"); $image->setImageFormat('png'); echo $image; } diff --git a/framework/yiilite.php b/framework/yiilite.php index 9a87a6de5a..b7d33cb4e0 100644 --- a/framework/yiilite.php +++ b/framework/yiilite.php @@ -40,7 +40,7 @@ class YiiBase private static $_logger; public static function getVersion() { - return '1.1.14-rc'; + return '1.1.14'; } public static function createWebApplication($config=null) { @@ -134,8 +134,14 @@ public static function import($alias,$forceInclude=false) return $alias; } else - throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory.', - array('{alias}'=>$namespace))); + { + // try to autoload the class with an autoloader + if (class_exists($alias,true)) + return self::$_imports[$alias]=$alias; + else + throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.', + array('{alias}'=>$namespace))); + } } if(($pos=strrpos($alias,'.'))===false) // a simple class name { @@ -2787,7 +2793,7 @@ public function sendFile($fileName,$content,$mimeType=null,$terminate=true) header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header("Content-type: $mimeType"); + header("Content-Type: $mimeType"); header('Content-Length: '.$length); header("Content-Disposition: attachment; filename=\"$fileName\""); header('Content-Transfer-Encoding: binary'); @@ -2821,7 +2827,7 @@ public function xSendFile($filePath, $options=array()) if(!isset($options['xHeader'])) $options['xHeader']='X-Sendfile'; if($options['mimeType']!==null) - header('Content-type: '.$options['mimeType']); + header('Content-Type: '.$options['mimeType']); header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"'); if(isset($options['addHeaders'])) { @@ -4444,9 +4450,6 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar public function init() { parent::init(); - // default session gc probability is 1% - ini_set('session.gc_probability',1); - ini_set('session.gc_divisor',100); if($this->autoStart) $this->open(); register_shutdown_function(array($this,'close')); diff --git a/framework/zii/widgets/CBaseListView.php b/framework/zii/widgets/CBaseListView.php index 7a6f5cc6b1..291942d437 100644 --- a/framework/zii/widgets/CBaseListView.php +++ b/framework/zii/widgets/CBaseListView.php @@ -82,6 +82,8 @@ abstract class CBaseListView extends CWidget public $emptyTagName='span'; /** * @var string the CSS class name for the container of all data item display. Defaults to 'items'. + * Note, this property must not contain false, null or empty string values. Otherwise such values may + * cause undefined behavior. */ public $itemsCssClass='items'; /** @@ -90,6 +92,8 @@ abstract class CBaseListView extends CWidget public $summaryCssClass='summary'; /** * @var string the CSS class name for the pager container. Defaults to 'pager'. + * Note, this property must not contain false, null or empty string values. Otherwise such values may + * cause undefined behavior. */ public $pagerCssClass='pager'; /** diff --git a/framework/zii/widgets/CDetailView.php b/framework/zii/widgets/CDetailView.php index 95f081b591..df8e08d576 100644 --- a/framework/zii/widgets/CDetailView.php +++ b/framework/zii/widgets/CDetailView.php @@ -77,7 +77,9 @@ class CDetailView extends CWidget * If the below "value" element is specified, this will be ignored. *
  51. value: the value to be displayed. If this is not specified, the above "name" element will be used * to retrieve the corresponding attribute value for display. Note that this value will be formatted according - * to the "type" option as described below.
  52. + * to the "type" option as described below. This can also be an anonymous function whose return value will be + * used as a value. The signature of the function should be function($data) where data refers to + * the {@link data} property of the detail view widget. *
  53. type: the type of the attribute that determines how the attribute value would be formatted. * Please see above for possible values. *
  54. cssClass: the CSS class to be used for this item. This option is available since version 1.1.3.
  55. @@ -209,7 +211,7 @@ public function run() if(!isset($attribute['type'])) $attribute['type']='text'; if(isset($attribute['value'])) - $value=is_callable($attribute['value']) ? call_user_func($attribute['value'],$this->data) : $attribute['value']; + $value=($attribute['value'] instanceof Closure) ? call_user_func($attribute['value'],$this->data) : $attribute['value']; elseif(isset($attribute['name'])) $value=CHtml::value($this->data,$attribute['name']); else diff --git a/framework/zii/widgets/CListView.php b/framework/zii/widgets/CListView.php index c6229456d0..80f15c5f50 100644 --- a/framework/zii/widgets/CListView.php +++ b/framework/zii/widgets/CListView.php @@ -22,7 +22,7 @@ * when the user browser disables JavaScript, the sorting and pagination automatically degenerate * to normal page requests and are still functioning as expected. * - * CListView should be used together with a {@link IDataProvider data provider}, preferrably a + * CListView should be used together with a {@link IDataProvider data provider}, preferably a * {@link CActiveDataProvider}. * * The minimal code needed to use CListView is as follows: diff --git a/framework/zii/widgets/CMenu.php b/framework/zii/widgets/CMenu.php index 79a3234362..bf14c430b8 100644 --- a/framework/zii/widgets/CMenu.php +++ b/framework/zii/widgets/CMenu.php @@ -22,7 +22,7 @@ * $this->widget('zii.widgets.CMenu', array( * 'items'=>array( * // Important: you need to specify url as 'controller/action', - * // not just as 'controller' even if default acion is used. + * // not just as 'controller' even if default action is used. * array('label'=>'Home', 'url'=>array('site/index')), * // 'Products' menu item will be selected no matter which tag parameter value is since it's not specified. * array('label'=>'Products', 'url'=>array('product/index'), 'items'=>array( @@ -46,8 +46,10 @@ class CMenu extends CWidget * @var array list of menu items. Each menu item is specified as an array of name-value pairs. * Possible option names include the following: *
      - *
    • label: string, optional, specifies the menu item label. When {@link encodeLabel} is true, the label + *
    • label: string, optional, specifies the menu item label. When {@link encodeLabel} or own encodeLabel option is true, the label * will be HTML-encoded. If the label is not specified, it defaults to an empty string.
    • + *
    • encodeLabel: boolean whether the label for menu item should be HTML-encoded. + * When this option is set, it will override the global setting {@link encodeLabel}. This option has been available since version 1.1.15.
    • *
    • url: string or array, optional, specifies the URL of the menu item. It is passed to {@link CHtml::normalizeUrl} * to generate a valid URL. If this is not set, the menu item will be rendered as a span text.
    • *
    • visible: boolean, optional, whether this menu item is visible. Defaults to true. @@ -269,7 +271,8 @@ protected function normalizeItems($items,$route,&$active) } if(!isset($item['label'])) $item['label']=''; - if($this->encodeLabel) + $encodeLabel = isset($item['encodeLabel']) ? $item['encodeLabel'] : $this->encodeLabel; + if($encodeLabel) $items[$i]['label']=CHtml::encode($item['label']); $hasActiveChild=false; if(isset($item['items'])) diff --git a/framework/zii/widgets/assets/gridview/jquery.yiigridview.js b/framework/zii/widgets/assets/gridview/jquery.yiigridview.js index af47c8f832..873c891e24 100644 --- a/framework/zii/widgets/assets/gridview/jquery.yiigridview.js +++ b/framework/zii/widgets/assets/gridview/jquery.yiigridview.js @@ -54,6 +54,8 @@ ajaxUpdate: [], ajaxVar: 'ajax', ajaxType: 'GET', + csrfTokenName: null, + csrfToken: null, pagerClass: 'pager', loadingClass: 'loading', filterClass: 'filters', @@ -315,6 +317,12 @@ options.data = $(settings.filterSelector).serialize(); } } + if (settings.csrfTokenName && settings.csrfToken) { + if (typeof options.data=='string') + options.data+='&'+settings.csrfTokenName+'='+settings.csrfToken; + else + options.data[settings.csrfTokenName] = settings.csrfToken; + } if(yiiXHR[id] != null){ yiiXHR[id].abort(); } diff --git a/framework/zii/widgets/assets/listview/jquery.yiilistview.js b/framework/zii/widgets/assets/listview/jquery.yiilistview.js index 9c8527a24f..c5c176aa05 100644 --- a/framework/zii/widgets/assets/listview/jquery.yiilistview.js +++ b/framework/zii/widgets/assets/listview/jquery.yiilistview.js @@ -174,7 +174,7 @@ $('#'+id).addClass(settings.loadingClass); if(settings.beforeAjaxUpdate != undefined) - settings.beforeAjaxUpdate(id); + settings.beforeAjaxUpdate(id, options); yiiXHR[id] = $.ajax(options); }; diff --git a/framework/zii/widgets/grid/CButtonColumn.php b/framework/zii/widgets/grid/CButtonColumn.php index 20ca54b38d..3b1623bf69 100644 --- a/framework/zii/widgets/grid/CButtonColumn.php +++ b/framework/zii/widgets/grid/CButtonColumn.php @@ -152,7 +152,7 @@ class CButtonColumn extends CGridColumn *
       	 *  array(
       	 *     class'=>'CButtonColumn',
      -	 *     'afterDelete'=>'function(link,success,data){ if(success) alert("Delete completed successfuly"); }',
      +	 *     'afterDelete'=>'function(link,success,data){ if(success) alert("Delete completed successfully"); }',
       	 *  ),
       	 * 
      */ @@ -305,13 +305,15 @@ protected function registerClientScript() } /** - * Renders the data cell content. + * Returns the data cell content. * This method renders the view, update and delete buttons in the data cell. * @param integer $row the row number (zero-based) - * @param mixed $data the data associated with the row + * @return string the data cell content. + * @since 1.1.15 */ - protected function renderDataCellContent($row,$data) + public function getDataCellContent($row) { + $data=$this->grid->dataProvider->data[$row]; $tr=array(); ob_start(); foreach($this->buttons as $id=>$button) @@ -321,7 +323,7 @@ protected function renderDataCellContent($row,$data) ob_clean(); } ob_end_clean(); - echo strtr($this->template,$tr); + return strtr($this->template,$tr); } /** diff --git a/framework/zii/widgets/grid/CCheckBoxColumn.php b/framework/zii/widgets/grid/CCheckBoxColumn.php index 5a582bdf44..9a2779ae87 100644 --- a/framework/zii/widgets/grid/CCheckBoxColumn.php +++ b/framework/zii/widgets/grid/CCheckBoxColumn.php @@ -187,43 +187,39 @@ public function init() } /** - * Renders the header cell content. + * Returns the header cell content. * This method will render a checkbox in the header when {@link selectableRows} is greater than 1 * or in case {@link selectableRows} is null when {@link CGridView::selectableRows} is greater than 1. + * @return string the header cell content. + * @since 1.1.15 */ - protected function renderHeaderCellContent() + public function getHeaderCellContent() { if(trim($this->headerTemplate)==='') - { - echo $this->grid->blankDisplay; - return; - } + return $this->grid->blankDisplay; - $item = ''; if($this->selectableRows===null && $this->grid->selectableRows>1) - $item = CHtml::checkBox($this->id.'_all',false,array('class'=>'select-on-check-all')); + $item=CHtml::checkBox($this->id.'_all',false,array('class'=>'select-on-check-all')); elseif($this->selectableRows>1) - $item = CHtml::checkBox($this->id.'_all',false); + $item=CHtml::checkBox($this->id.'_all',false); else - { - ob_start(); - parent::renderHeaderCellContent(); - $item = ob_get_clean(); - } + $item=parent::getHeaderCellContent(); - echo strtr($this->headerTemplate,array( + return strtr($this->headerTemplate,array( '{item}'=>$item, )); } /** - * Renders the data cell content. + * Returns the data cell content. * This method renders a checkbox in the data cell. * @param integer $row the row number (zero-based) - * @param mixed $data the data associated with the row + * @return string the data cell content. + * @since 1.1.15 */ - protected function renderDataCellContent($row,$data) + public function getDataCellContent($row) { + $data=$this->grid->dataProvider->data[$row]; if($this->value!==null) $value=$this->evaluateExpression($this->value,array('data'=>$data,'row'=>$row)); elseif($this->name!==null) @@ -243,6 +239,6 @@ protected function renderDataCellContent($row,$data) unset($options['name']); $options['value']=$value; $options['id']=$this->id.'_'.$row; - echo CHtml::checkBox($name,$checked,$options); + return CHtml::checkBox($name,$checked,$options); } } diff --git a/framework/zii/widgets/grid/CDataColumn.php b/framework/zii/widgets/grid/CDataColumn.php index 7d2abc91fd..92c90760be 100644 --- a/framework/zii/widgets/grid/CDataColumn.php +++ b/framework/zii/widgets/grid/CDataColumn.php @@ -85,54 +85,59 @@ public function init() } /** - * Renders the filter cell content. - * This method will render the {@link filter} as is if it is a string. + * Returns the filter cell content. + * This method will return the {@link filter} as is if it is a string. * If {@link filter} is an array, it is assumed to be a list of options, and a dropdown selector will be rendered. * Otherwise if {@link filter} is not false, a text field is rendered. - * @since 1.1.1 + * @return string the filter cell content + * @since 1.1.15 */ - protected function renderFilterCellContent() + public function getFilterCellContent() { if(is_string($this->filter)) - echo $this->filter; + return $this->filter; elseif($this->filter!==false && $this->grid->filter!==null && $this->name!==null && strpos($this->name,'.')===false) { if(is_array($this->filter)) - echo CHtml::activeDropDownList($this->grid->filter, $this->name, $this->filter, array('id'=>false,'prompt'=>'')); + return CHtml::activeDropDownList($this->grid->filter, $this->name, $this->filter, array('id'=>false,'prompt'=>'')); elseif($this->filter===null) - echo CHtml::activeTextField($this->grid->filter, $this->name, array('id'=>false)); + return CHtml::activeTextField($this->grid->filter, $this->name, array('id'=>false)); } else - parent::renderFilterCellContent(); + return parent::getFilterCellContent(); } /** - * Renders the header cell content. + * Returns the header cell content. * This method will render a link that can trigger the sorting if the column is sortable. + * @return string the header cell content. + * @since 1.1.15 */ - protected function renderHeaderCellContent() + public function getHeaderCellContent() { if($this->grid->enableSorting && $this->sortable && $this->name!==null) - echo $this->grid->dataProvider->getSort()->link($this->name,$this->header,array('class'=>'sort-link')); + return $this->grid->dataProvider->getSort()->link($this->name,$this->header,array('class'=>'sort-link')); elseif($this->name!==null && $this->header===null) { if($this->grid->dataProvider instanceof CActiveDataProvider) - echo CHtml::encode($this->grid->dataProvider->model->getAttributeLabel($this->name)); + return CHtml::encode($this->grid->dataProvider->model->getAttributeLabel($this->name)); else - echo CHtml::encode($this->name); + return CHtml::encode($this->name); } else - parent::renderHeaderCellContent(); + return parent::getHeaderCellContent(); } /** - * Renders the data cell content. + * Returns the data cell content. * This method evaluates {@link value} or {@link name} and renders the result. * @param integer $row the row number (zero-based) - * @param mixed $data the data associated with the row + * @return string the data cell content. + * @since 1.1.15 */ - protected function renderDataCellContent($row,$data) + public function getDataCellContent($row) { + $data=$this->grid->dataProvider->data[$row]; if($this->value!==null) $value=$this->evaluateExpression($this->value,array('data'=>$data,'row'=>$row)); elseif($this->name!==null) diff --git a/framework/zii/widgets/grid/CGridColumn.php b/framework/zii/widgets/grid/CGridColumn.php index 48e3d613af..2ebd8e57ad 100644 --- a/framework/zii/widgets/grid/CGridColumn.php +++ b/framework/zii/widgets/grid/CGridColumn.php @@ -20,6 +20,9 @@ * * @property boolean $hasFooter Whether this column has a footer cell. * This is determined based on whether {@link footer} is set. + * @property string $filterCellContent The filter cell content. + * @property string $headerCellContent The header cell content. + * @property string $footerCellContent The footer cell content. * * @author Qiang Xue * @package zii.widgets.grid @@ -163,44 +166,90 @@ public function renderFooterCell() } /** - * Renders the header cell content. - * The default implementation simply renders {@link header}. + * Returns the header cell content. + * The default implementation simply returns {@link header}. * This method may be overridden to customize the rendering of the header cell. + * @return string the header cell content. + * @since 1.1.15 + */ + public function getHeaderCellContent() + { + return trim($this->header)!=='' ? $this->header : $this->grid->blankDisplay; + } + + /** + * Renders the header cell content. + * @deprecated since 1.1.15. Use {@link getHeaderCellContent()} instead. */ protected function renderHeaderCellContent() { - echo trim($this->header)!=='' ? $this->header : $this->grid->blankDisplay; + echo $this->getHeaderCellContent(); } /** - * Renders the footer cell content. - * The default implementation simply renders {@link footer}. + * Returns the footer cell content. + * The default implementation simply returns {@link footer}. * This method may be overridden to customize the rendering of the footer cell. + * @return string the footer cell content. + * @since 1.1.15 + */ + public function getFooterCellContent() + { + return trim($this->footer)!=='' ? $this->footer : $this->grid->blankDisplay; + } + + /** + * Renders the footer cell content. + * @deprecated since 1.1.15. Use {@link getFooterCellContent()} instead. */ protected function renderFooterCellContent() { - echo trim($this->footer)!=='' ? $this->footer : $this->grid->blankDisplay; + echo $this->getFooterCellContent(); } /** - * Renders the data cell content. + * Returns the data cell content. * This method SHOULD be overridden to customize the rendering of the data cell. * @param integer $row the row number (zero-based) + * The data for this row is available via $this->grid->dataProvider->data[$row]; + * @return string the data cell content. + * @since 1.1.15 + */ + public function getDataCellContent($row) + { + return $this->grid->blankDisplay; + } + + /** + * Renders the data cell content. + * @param integer $row the row number (zero-based) * @param mixed $data the data associated with the row + * @deprecated since 1.1.15. Use {@link getDataCellContent()} instead. */ protected function renderDataCellContent($row,$data) { - echo $this->grid->blankDisplay; + echo $this->getDataCellContent($row); } /** - * Renders the filter cell content. - * The default implementation simply renders a space. + * Returns the filter cell content. + * The default implementation simply returns an empty column. * This method may be overridden to customize the rendering of the filter cell (if any). + * @return string the filter cell content. + * @since 1.1.15 + */ + public function getFilterCellContent() + { + return $this->grid->blankDisplay; + } + + /** + * Renders the filter cell content. * @since 1.1.1 + * @deprecated since 1.1.15. Use {@link getFilterCellContent()} instead. */ protected function renderFilterCellContent() { - echo $this->grid->blankDisplay; + echo $this->getFilterCellContent(); } } diff --git a/framework/zii/widgets/grid/CGridView.php b/framework/zii/widgets/grid/CGridView.php index 1a44182aaf..e1323dbfe0 100644 --- a/framework/zii/widgets/grid/CGridView.php +++ b/framework/zii/widgets/grid/CGridView.php @@ -25,7 +25,7 @@ * when the user browser disables JavaScript, the sorting and pagination automatically degenerate * to normal page requests and are still functioning as expected. * - * CGridView should be used together with a {@link IDataProvider data provider}, preferrably a + * CGridView should be used together with a {@link IDataProvider data provider}, preferably a * {@link CActiveDataProvider}. * * The minimal code needed to use CGridView is as follows: @@ -182,7 +182,7 @@ class CGridView extends CBaseListView * Possible values (besides null) are "timeout", "error", "notmodified" and "parsererror"
    • *
    • errorThrown is an optional exception object, if one occurred.
    • *
    • errorMessage is the CGridView default error message derived from xhr and errorThrown. - * Usefull if you just want to display this error differently. CGridView by default displays this error with an javascript.alert()
    • + * Useful if you just want to display this error differently. CGridView by default displays this error with an javascript.alert() *
    * Note: This handler is not called for JSONP requests, because they do not use an XMLHttpRequest. * @@ -444,8 +444,14 @@ public function registerClientScript() ); if($this->ajaxUrl!==null) $options['url']=CHtml::normalizeUrl($this->ajaxUrl); - if($this->ajaxType!==null) + if($this->ajaxType!==null) { $options['ajaxType']=strtoupper($this->ajaxType); + $request=Yii::app()->getRequest(); + if ($options['ajaxType']=='POST' && $request->enableCsrfValidation) { + $options['csrfTokenName']=$request->csrfTokenName; + $options['csrfToken']=$request->getCsrfToken(); + } + } if($this->enablePagination) $options['pageVar']=$this->dataProvider->getPagination()->pageVar; foreach(array('beforeAjaxUpdate', 'afterAjaxUpdate', 'ajaxUpdateError', 'selectionChanged') as $event) diff --git a/framework/zii/widgets/grid/CLinkColumn.php b/framework/zii/widgets/grid/CLinkColumn.php index f73b8a23af..12a73c3d4f 100644 --- a/framework/zii/widgets/grid/CLinkColumn.php +++ b/framework/zii/widgets/grid/CLinkColumn.php @@ -87,13 +87,15 @@ class CLinkColumn extends CGridColumn public $linkHtmlOptions=array(); /** - * Renders the data cell content. + * Returns the data cell content. * This method renders a hyperlink in the data cell. * @param integer $row the row number (zero-based) - * @param mixed $data the data associated with the row + * @return string the data cell content. + * @since 1.1.15 */ - protected function renderDataCellContent($row,$data) + public function getDataCellContent($row) { + $data=$this->grid->dataProvider->data[$row]; if($this->urlExpression!==null) $url=$this->evaluateExpression($this->urlExpression,array('data'=>$data,'row'=>$row)); else diff --git a/framework/zii/widgets/jui/CJuiTabs.php b/framework/zii/widgets/jui/CJuiTabs.php index 72317c8661..cb98af2ced 100644 --- a/framework/zii/widgets/jui/CJuiTabs.php +++ b/framework/zii/widgets/jui/CJuiTabs.php @@ -82,7 +82,7 @@ class CJuiTabs extends CJuiWidget * The token "{title}" in the template will be replaced with the panel title and * the token "{url}" will be replaced with "#TabID" or with the url of the ajax request. */ - public $headerTemplate='
  56. {title}
  57. '; + public $headerTemplate='
  58. {title}
  59. '; /** * @var string the template that is used to generated every tab content. * The token "{content}" in the template will be replaced with the panel content diff --git a/requirements/index.php b/requirements/index.php index 4090a39b3c..9ce1381ee7 100644 --- a/requirements/index.php +++ b/requirements/index.php @@ -119,6 +119,12 @@ class_exists("DOMDocument",false), extension_loaded("mcrypt"), 'CSecurityManager', t('yii','Required by encrypt and decrypt methods.')), + array( + t('yii','crypt() CRYPT_BLOWFISH option'), + false, + function_exists('crypt') && defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH, + 'CPasswordHelper', + t('yii','Required for secure password storage.')), array( t('yii','SOAP extension'), false, diff --git a/requirements/messages/config.php b/requirements/messages/config.php index 45c41d0ad1..0ce500bc88 100644 --- a/requirements/messages/config.php +++ b/requirements/messages/config.php @@ -6,7 +6,7 @@ return array( 'sourcePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', 'messagePath'=>dirname(__FILE__), - 'languages'=>array('zh_cn','zh_tw','de','es','el','sv','he','nl','pt','ru','it','fr','ja','pl','hu','ro','id','vi','bg','uk','cs'), + 'languages'=>array('zh_cn','zh_tw','de','es','el','sv','he','nl','pt','ru','it','fr','ja','pl','hu','ro','id','vi','bg','uk','cs','da'), 'fileTypes'=>array('php'), 'overwrite'=>true, 'translator'=>'t', diff --git a/requirements/messages/da/yii.php b/requirements/messages/da/yii.php new file mode 100644 index 0000000000..430114de89 --- /dev/null +++ b/requirements/messages/da/yii.php @@ -0,0 +1,61 @@ + '$_SERVER indeholder ikke {vars}.', + '$_SERVER variable' => '$_SERVER variabel', + '$_SERVER["SCRIPT_FILENAME"] must be the same as the entry script file path.' => '$_SERVER["SCRIPT_FILENAME"] skal være som stien til startscriptet.', + 'APC extension' => 'APC extension', + 'All DB-related classes' => 'Alle DB-relaterede klasser', + 'Ctype extension' => 'Ctype extension', + 'DOM extension' => 'DOM extension', + 'Either $_SERVER["REQUEST_URI"] or $_SERVER["QUERY_STRING"] must exist.' => '$_SERVER["REQUEST_URI"] eller $_SERVER["QUERY_STRING"] skal eksistere.', + 'Fileinfo extension' => 'Fileinfo extension', + 'GD extension with
    FreeType support
    or ImageMagick
    extension with
    PNG support' => 'GD extension med
    FreeType support
    eller ImageMagick
    extension med
    PNG support', + 'GD installed,
    FreeType support not installed' => 'GD installeret,
    FreeType support er ikke installeret', + 'GD or ImageMagick not installed' => 'GD eller ImageMagick er ikke installeret', + 'Mcrypt extension' => 'Mcrypt extension', + 'Memcache extension' => 'Memcache extension', + 'PCRE extension' => 'PCRE extension', + 'PDO MSSQL extension (pdo_sqlsrv)' => 'PDO MSSQL extension (pdo_sqlsrv)', + 'PDO MSSQL extension (pdo_dblib)' => 'PDO MSSQL extension (pdo_dblib)', + 'PDO MSSQL extension (pdo_mssql)' => 'PDO MSSQL extension (pdo_mssql)', + 'PDO MySQL extension' => 'PDO MYSQL extension', + 'PDO Oracle extension' => 'PDO Oracle extension', + 'PDO PostgreSQL extension' => 'PDO PostgreSQL extension', + 'PDO SQLite extension' => 'PDO SQLite extension', + 'PDO extension' => 'PDO extension', + 'PHP 5.1.0 or higher is required.' => 'PHP 5.1.0 eller nyere er påkrævet.', + 'PHP version' => 'PHP version', + 'Reflection extension' => 'Reflection extension', + 'Required by encrypt and decrypt methods.' => 'Påkrævet af encrypt og decrypt funktioner.', + 'Required for MIME-type validation' => 'Påkrævet for MIME-type validering', + 'Required for MSSQL database from GNU/Linux or other UNIX.' => 'Påkrævet for MSSQL database fra GNU/Linux eller andre UNIX.', + 'Required for MSSQL database from MS Windows' => 'Påkrævet for MSSQL database fra MS Windows', + 'Required for MSSQL database with the driver provided by Microsoft.' => 'Påkrævet for MSSQL database med driveren leveret af Microsoft.', + 'Required for MySQL database.' => 'Påkrævet for MySQL database.', + 'Required for Oracle database.' => 'Påkrævet for Oracle database.', + 'Required for PostgreSQL database.' => 'Påkrævet for PostgreSQL database.', + 'Required for SQLite database.' => 'Påkrævet for SQLite database.', + 'Required for secure password storage.' => 'Påkrævet for sikkert kodeordslager.', + 'SOAP extension' => 'SOAP extension', + 'SPL extension' => 'SPL extension', + 'To use memcached set CMemCache::useMemcached to true.' => 'For at bruge memcached sæt CMemCache::useMemcached til true.', + 'Unable to determine URL path info. Please make sure $_SERVER["PATH_INFO"] (or $_SERVER["PHP_SELF"] and $_SERVER["SCRIPT_NAME"]) contains proper value.' => 'Kunne ikke bestemme path info. Venligst tjek at $_SERVER["PATH_INFO"] (eller $_SERVER["PHP_SELF"] og $_SERVER["SCRIPT_NAME"]) indeholder passende værdier.', + 'crypt() CRYPT_BLOWFISH option' => 'crypt() CRYPT_BLOWFISH option', +); diff --git a/requirements/messages/es/yii.php b/requirements/messages/es/yii.php index 2b113e2f82..2048fb1c31 100644 --- a/requirements/messages/es/yii.php +++ b/requirements/messages/es/yii.php @@ -11,32 +11,49 @@ * Messages that no longer need translation will have their translations * enclosed between a pair of '@@' marks. * + * Message string can be used with plural forms format. Check i18n section + * of the guide for details. + * * NOTE, this file must be saved in UTF-8 encoding. */ return array ( '$_SERVER does not have {vars}.' => '$_SERVER no contiene {vars}', '$_SERVER variable' => 'variable de $_SERVER', - '$_SERVER["SCRIPT_FILENAME"] must be the same as the entry script file path.' => '$_SERVER["SCRIPT_FILENAME"] debe ser igual a la ruta de entrada del script', + '$_SERVER["SCRIPT_FILENAME"] must be the same as the entry script file path.' => '$_SERVER["SCRIPT_FILENAME"] debe ser igual a la ruta del script de entrada', 'APC extension' => 'Extensión APC', 'All DB-related classes' => 'Todas las clases relacionadas con DB', + 'Ctype extension' => 'Extensión Ctype', 'DOM extension' => 'Extensión DOM', 'Either $_SERVER["REQUEST_URI"] or $_SERVER["QUERY_STRING"] must exist.' => '$_SERVER["REQUEST_URI"] o $_SERVER["QUERY_STRING"] deben existir.', - 'GD extension' => 'Extensión GD', + 'Fileinfo extension' => 'Extensión Fileinfo', + 'GD extension with
    FreeType support
    or ImageMagick
    extension with
    PNG support' => 'Extensión GD con
    soporte FreeType
    o extensión
    ImageMagick con
    soporte PNG', + 'GD installed,
    FreeType support not installed' => 'GD instalado,
    soporte FreeType no instalado', + 'GD or ImageMagick not installed' => 'GD o ImageMagick no instalados', 'Mcrypt extension' => 'Extensión Mcrypt', 'Memcache extension' => 'Extensión Memcache', 'PCRE extension' => 'Extensión PCRE', + 'PDO MSSQL extension (pdo_sqlsrv)' => 'Extensión PDO MSSQL (pdo_sqlsrv)', + 'PDO MSSQL extension (pdo_dblib)' => 'Extensión PDO MSSQL (pdo_dblib)', + 'PDO MSSQL extension (pdo_mssql)' => 'Extensión PDO MSSQL (pdo_mssql)', 'PDO MySQL extension' => 'Extensión PDO MySQL', + 'PDO Oracle extension' => 'Extensión PDO Oracle ', 'PDO PostgreSQL extension' => 'Extension PDO PostgreSQL', 'PDO SQLite extension' => 'Extensión PDO SQLite', 'PDO extension' => 'Extensión PDO', 'PHP 5.1.0 or higher is required.' => 'Requiere PHP 5.1.0 o superior', 'PHP version' => 'Versión PHP', 'Reflection extension' => 'Extensión Reflection', + 'Required by encrypt and decrypt methods.' => 'Requerida para los métodos encrypt y decrypt', + 'Required for MIME-type validation' => 'Requerida para validación de tipos MIME', + 'Required for MSSQL database from GNU/Linux or other UNIX.' => 'Requerida para base de datos MSSQL desde GNU/Linux u otro UNIX.', + 'Required for MSSQL database from MS Windows' => 'Requerida para base de datos MSSQL desde MS Windows', + 'Required for MSSQL database with the driver provided by Microsoft.' => 'Requerida para base de datos MSSQL con el manejador provisto por Microsoft.', + 'Required for MySQL database.' => 'Requerida para base de datos MySQL', + 'Required for Oracle database.' => 'Requerida para base de datos Oracle', + 'Required for PostgreSQL database.' => 'Requerida para base de datos PostgreSQL', + 'Required for SQLite database.' => 'Requerida para base de datos SQLite', 'SOAP extension' => 'Extensión SOAP', 'SPL extension' => 'Extensión SPL', - 'This is required if you are using MySQL database.' => 'Esto es requerido si se utiliza una base de datos MySQL.', - 'This is required if you are using PostgreSQL database.' => 'Esto es requerido si se utiliza una base de datos PostgreSQL.', - 'This is required if you are using SQLite database.' => 'Esto es requerido si se utiliza una base de datos SQLite.', - 'Unable to determine URL path info. Please make sure $_SERVER["PATH_INFO"] (or $_SERVER["PHP_SELF"] and $_SERVER["SCRIPT_NAME"]) contains proper value.' - => 'No es posible determinar la ruta de información. Por favor asegúrese que $_SERVER["PATH_INFO"] o ($_SERVER["PHP_SELF"] y $_SERVER["SCRIPT_NAME"]) contenga/n un valor correcto.', + 'To use memcached set CMemCache::useMemcached to true.' => 'Para usar memcached asigna CMemCache::useMemcached a true.', + 'Unable to determine URL path info. Please make sure $_SERVER["PATH_INFO"] (or $_SERVER["PHP_SELF"] and $_SERVER["SCRIPT_NAME"]) contains proper value.' => 'No es posible determinar la ruta de información del URL. Por favor asegúrese que $_SERVER["PATH_INFO"] o ($_SERVER["PHP_SELF"] y $_SERVER["SCRIPT_NAME"]) contenga/n un valor correcto.', ); diff --git a/requirements/messages/ja/yii.php b/requirements/messages/ja/yii.php index 573b1f2f86..3b134e408f 100644 --- a/requirements/messages/ja/yii.php +++ b/requirements/messages/ja/yii.php @@ -52,8 +52,10 @@ 'Required for Oracle database.' => 'Oracle データベースを使用する場合に必要。', 'Required for PostgreSQL database.' => 'PostgreSQL データベースを使用する場合に必要。', 'Required for SQLite database.' => 'SQLite データベースを使用する場合に必要。', + 'Required for secure password storage.' => '安全なパスワード保存に必要。', 'SOAP extension' => 'SOAP 拡張', 'SPL extension' => 'SPL 拡張', 'To use memcached set CMemCache::useMemcached to true.' => 'memcached を使用するためには CMemCache::useMemcachedtrue に設定して下さい。', 'Unable to determine URL path info. Please make sure $_SERVER["PATH_INFO"] (or $_SERVER["PHP_SELF"] and $_SERVER["SCRIPT_NAME"]) contains proper value.' => 'URL パス情報を決定できません。$_SERVER["PATH_INFO"] (もしくは $_SERVER["PHP_SELF"] と $_SERVER["SCRIPT_NAME"]) が正しい値かどうか確認してください。', + 'crypt() CRYPT_BLOWFISH option' => 'crypt() CRYPT_BLOWFISH オプション', ); diff --git a/requirements/views/da/index.php b/requirements/views/da/index.php new file mode 100644 index 0000000000..f0cb2f88bf --- /dev/null +++ b/requirements/views/da/index.php @@ -0,0 +1,77 @@ + + + + + + +Yii - Tjek systemkrav + + + +
    + + + +
    +

    Beskrivelse

    +

    +Dette script tjekker om serverkonfigurationen opfylder kravene for at køre +Yii-applikationer. +Det tjekker om serveren kører korrekt version af PHP, om nødvendige extensions +er indlæst og om PHP-indstillingerne i php.ini er korrekte. +

    + +

    Konklusion

    +

    +0): ?> +Tillykke! Konfigurationen på serveren tilfredsstiller alle krav for at køre Yii. + +Konfigurationen på serveren tilfredsstiller minimumskravene til Yii. Vær opmærksom +på advarslerne listet nedenfor, i tilfælde af at din applikation anvender nogen af +funktionerne. + +Desværre tilfredsstiller konfigurationen af serveren ikke minimumskravene til Yii. + +

    + +

    Detaljer

    + + + + + + + + + + + +
    NavnResultatKræves afNotat
    + + + + + + + +
    + + + + + + + +
     ok fejl advarsel
    + +
    + + + +
    + + \ No newline at end of file diff --git a/tests/framework/caching/CFileCacheTest.php b/tests/framework/caching/CFileCacheTest.php index 82ca716672..4ba793684d 100644 --- a/tests/framework/caching/CFileCacheTest.php +++ b/tests/framework/caching/CFileCacheTest.php @@ -2,6 +2,68 @@ class CFileCacheTest extends CTestCase { + private $cachePathModes=array( + 0777,0775,0770,0755,0750,0700 + ); + + private $cacheFileModes=array( + 0666,0664,0660,0644,0640,0600 + ); + + private $cachePath; + + public function setUp() + { + $this->cachePath=Yii::getPathOfAlias('application.runtime.CFileCacheTest'); + if(!is_dir($this->cachePath) && !(@mkdir($this->cachePath))) + $this->markTestIncomplete('Unit tests runtime directory should have writable permissions!'); + + if (substr(PHP_OS,0,3)=='WIN') + $this->markTestSkipped("Can't reliably test it on Windows because fileperms() always return 0777."); + } + + public function testPathMode() + { + foreach ($this->cachePathModes as $testMode) + { + $this->removeDirectory($this->cachePath); + $app=new TestApplication(array( + 'id'=>'testApp', + 'components'=>array( + 'cache'=>array('class'=>'CFileCache','cachePath'=>$this->cachePath,'cachePathMode'=>$testMode), + ), + )); + /** @var CFileCache $cache */ + $cache=$app->cache; + + $this->assertTrue(is_dir($cache->cachePath)); + $this->assertEquals(sprintf('%04o',$testMode),$this->getMode($cache->cachePath)); + } + } + + public function testFileMode() + { + foreach ($this->cacheFileModes as $testMode) + { + $this->removeDirectory($this->cachePath); + $app=new TestApplication(array( + 'id'=>'testApp', + 'components'=>array( + 'cache'=>array('class'=>'CFileCache','cachePath'=>$this->cachePath,'cacheFileMode'=>$testMode), + ), + )); + /** @var CFileCache $cache */ + $cache=$app->cache; + + $cache->set('testKey1','testValue1'); + $files=glob($cache->cachePath.'/*.bin'); + $file=array_shift($files); + + $this->assertTrue(is_file($file)); + $this->assertEquals(sprintf('%04o',$testMode),$this->getMode($file)); + } + } + /** * https://github.com/yiisoft/yii/issues/2435 */ @@ -10,14 +72,14 @@ public function testEmbedExpiry() $app=new TestApplication(array( 'id'=>'testApp', 'components'=>array( - 'cache'=>array('class'=>'CFileCache'), + 'cache'=>array('class'=>'CFileCache','cachePath'=>$this->cachePath), ), )); $app->reset(); $cache=$app->cache; $cache->set('testKey1','testValue1',2); - $files=glob(Yii::getPathOfAlias('application.runtime.cache').'/*.bin'); + $files=glob($cache->cachePath.'/*.bin'); $this->assertEquals(time()+2,filemtime($files[0])); $cache->set('testKey2','testValue2',2); @@ -32,14 +94,14 @@ public function testEmbedExpiry() $app=new TestApplication(array( 'id'=>'testApp', 'components'=>array( - 'cache'=>array('class'=>'CFileCache','embedExpiry'=>true), + 'cache'=>array('class'=>'CFileCache','cachePath'=>$this->cachePath,'embedExpiry'=>true), ), )); $app->reset(); $cache=$app->cache; $cache->set('testKey4','testValue4',2); - $files=glob(Yii::getPathOfAlias('application.runtime.cache').'/*.bin'); + $files=glob($cache->cachePath.'/*.bin'); $this->assertEquals(time(),filemtime($files[0])); $cache->set('testKey5','testValue5',2); @@ -50,4 +112,31 @@ public function testEmbedExpiry() sleep(3); $this->assertEquals(false,$cache->get('testKey6')); } + + public function tearDown() + { + $this->removeDirectory($this->cachePath); + } + + private function getMode($file) + { + return substr(sprintf('%04o',fileperms($file)),-4); + } + + /** @see CFileHelper::removeDirectory */ + private function removeDirectory($directory) + { + $items=glob($directory.DIRECTORY_SEPARATOR.'{,.}*',GLOB_MARK | GLOB_BRACE); + foreach($items as $item) + { + if(basename($item)=='.' || basename($item)=='..') + continue; + if(substr($item,-1)==DIRECTORY_SEPARATOR) + self::removeDirectory($item); + else + unlink($item); + } + if(is_dir($directory)) + rmdir($directory); + } } diff --git a/tests/framework/caching/CRedisCacheTest.php b/tests/framework/caching/CRedisCacheTest.php index 4eacc5aaa9..fdf4877b67 100755 --- a/tests/framework/caching/CRedisCacheTest.php +++ b/tests/framework/caching/CRedisCacheTest.php @@ -129,4 +129,43 @@ public function testFlush() $this->assertTrue($cache->flush()); $this->assertFalse($cache->get('number_test')); } + + /** + * Store a value that is 2 times buffer size big + * https://github.com/yiisoft/yii/pull/2750 + */ + public function testLargeData() + { + $app=$this->getApplication(); + $cache=$app->cache; + + $data=str_repeat('XX',8192); // http://www.php.net/manual/en/function.fread.php + $key='bigdata1'; + + $this->assertFalse($cache->get($key)); + $cache->set($key,$data); + $this->assertTrue($cache->get($key)===$data); + + // try with multibyte string + $data=str_repeat('ЖЫ',8192); // http://www.php.net/manual/en/function.fread.php + $key='bigdata2'; + + $this->assertFalse($cache->get($key)); + $cache->set($key,$data); + $this->assertTrue($cache->get($key)===$data); + } + + public function testMultiByteGetAndSet() + { + $app=$this->getApplication(); + $cache=$app->cache; + + $data=array('abc'=>'ежик',2=>'def'); + $key='data1'; + + $this->assertFalse($cache->get($key)); + $cache->set($key,$data); + $this->assertTrue($cache->get($key)===$data); + } + } diff --git a/tests/framework/db/CDbCommand2Test.php b/tests/framework/db/CDbCommand2Test.php index 0a268f4fc8..29906945ac 100644 --- a/tests/framework/db/CDbCommand2Test.php +++ b/tests/framework/db/CDbCommand2Test.php @@ -239,6 +239,16 @@ public function testJoin() $command->naturalJoin('user'); $join[]='NATURAL JOIN \'user\''; $this->assertEquals($join, $command->join); + + // natural left join + $command->naturalLeftJoin('user'); + $join[]='NATURAL LEFT JOIN \'user\''; + $this->assertEquals($join, $command->join); + + // natural right join + $command->naturalRightJoin('user'); + $join[]='NATURAL RIGHT JOIN \'user\''; + $this->assertEquals($join, $command->join); } public function testGroup() diff --git a/tests/framework/db/ar/CActiveRecordTest.php b/tests/framework/db/ar/CActiveRecordTest.php index de92dad7b4..bc2a6290d4 100644 --- a/tests/framework/db/ar/CActiveRecordTest.php +++ b/tests/framework/db/ar/CActiveRecordTest.php @@ -4,6 +4,7 @@ Yii::import('system.db.ar.CActiveRecord'); require_once(dirname(__FILE__).'/../data/models.php'); +require_once(dirname(__FILE__).'/../data/models2.php'); class CActiveRecordTest extends CTestCase { @@ -713,6 +714,43 @@ public function testRelationalStat() $this->assertEquals(4,count($users)); } + /** + * @depends testRelationalStat + * @see https://github.com/yiisoft/yii/issues/873 + */ + public function testRelationalStatWithScopes() + { + // CStatRelation with scopes, HAS_MANY case + $users=User::model()->findAll(); + // user1 + $this->assertEquals(0,$users[0]->recentPostCount1); + $this->assertEquals(0,$users[0]->recentPostCount2); + // user2 + $this->assertEquals(2,$users[1]->recentPostCount1); + $this->assertEquals(2,$users[1]->recentPostCount2); + // user3 + $this->assertEquals(1,$users[2]->recentPostCount1); + $this->assertEquals(1,$users[2]->recentPostCount2); + // user4 + $this->assertEquals(0,$users[3]->recentPostCount1); + $this->assertEquals(0,$users[3]->recentPostCount2); + + // CStatRelation with scopes, MANY_MANY case + $categories=Category::model()->findAll(); + // category1 + $this->assertEquals(2,$categories[0]->recentPostCount1); + $this->assertEquals(2,$categories[0]->recentPostCount2); + // category2 + $this->assertEquals(0,$categories[1]->recentPostCount1); + $this->assertEquals(0,$categories[1]->recentPostCount2); + // category3 + $this->assertEquals(0,$categories[2]->recentPostCount1); + $this->assertEquals(0,$categories[2]->recentPostCount2); + // category4 + $this->assertEquals(1,$categories[3]->recentPostCount1); + $this->assertEquals(1,$categories[3]->recentPostCount2); + } + public function testLazyLoadingWithConditions() { $user=User::model()->findByPk(2); @@ -1369,6 +1407,26 @@ public function testIssue206() $this->assertEquals($result1, $result2); } + /** + * @see https://github.com/yiisoft/yii/issues/268 + */ + public function testCountIsSubStringOfFieldName() + { + $result = User::model()->with('profiles')->count(array('select'=>'country AS country','condition'=>'t.id=2')); + $this->assertEquals(1,$result); + } + + /** + * verify https://github.com/yiisoft/yii/issues/2756 + */ + public function testLazyFindCondition() + { + $user = User::model()->findByPk(2); + $this->assertEquals(3, count($user->posts())); + $this->assertEquals(2, count($user->posts(array('condition' => 'id IN (2,3)')))); + $this->assertEquals(2, count($user->postsCondition())); + } + /** * https://github.com/yiisoft/yii/issues/1070 */ @@ -1500,4 +1558,23 @@ public function testThroughBelongsEager() $this->assertTrue($comment->postAuthor->equals($comment->postAuthorBelongsTo)); } } + + public function testNamespacedTableName() + { + if(!version_compare(PHP_VERSION,"5.3.0",">=")) + $this->markTestSkipped('PHP 5.3.0 or higher required for namespaces.'); + require_once(dirname(__FILE__).'/../data/models-namespaced.php'); + $this->assertEquals("posts",Post::model()->tableName()); + $this->assertEquals("Example",CActiveRecord::model("yiiArExample\\testspace\\Example")->tableName()); + } + + /** + * https://github.com/yiisoft/yii/issues/2884 + */ + public function testDefaultScopeAlias() + { + $this->assertEquals('user3', UserWithDefaultScopeAlias::model()->resetScope()->findByPk(3)->username); + $this->assertNull(UserWithDefaultScopeAlias::model()->findByPk(3)); + $this->assertNotNull(UserWithDefaultScopeAlias::model()->findByPk(1)); + } } diff --git a/tests/framework/db/data/models-namespaced.php b/tests/framework/db/data/models-namespaced.php new file mode 100644 index 0000000000..78fa3c9c88 --- /dev/null +++ b/tests/framework/db/data/models-namespaced.php @@ -0,0 +1,7 @@ +array(self::HAS_MANY,'Group',array('group_id'=>'id'),'through'=>'roles'), 'mentorships'=>array(self::HAS_MANY,'Mentorship','teacher_id','joinType'=>'INNER JOIN'), 'students'=>array(self::HAS_MANY,'User',array('student_id'=>'id'),'through'=>'mentorships','joinType'=>'INNER JOIN'), + 'profiles'=>array(self::HAS_MANY,'Profile','user_id'), 'posts'=>array(self::HAS_MANY,'Post','author_id'), + 'postsCondition'=>array(self::HAS_MANY,'Post','author_id', 'condition'=>'postsCondition.id IN (2,3)'), 'postsOrderDescFormat1'=>array(self::HAS_MANY,'Post','author_id','scopes'=>'orderDesc'), 'postsOrderDescFormat2'=>array(self::HAS_MANY,'Post','author_id','scopes'=>array('orderDesc')), 'postCount'=>array(self::STAT,'Post','author_id'), /* For {@link CActiveRecordTest::testHasManyThroughHasManyWithCustomSelect()}: */ 'mentorshipsCustomSelect'=>array(self::HAS_MANY,'Mentorship','teacher_id','select' => array('teacher_id', 'student_id')), 'studentsCustomSelect'=>array(self::HAS_MANY,'User',array('student_id'=>'id'),'through'=>'mentorshipsCustomSelect','select' => array('id', 'username')), + /* For {@link CActiveRecordTest::testRelationalStatWithScopes}: */ + 'recentPostCount1'=>array(self::STAT,'Post','author_id','scopes'=>'recentScope'), // CStatRelation with scopes, HAS_MANY case + 'recentPostCount2'=>array(self::STAT,'Post','author_id','scopes'=>array('recentScope')), // CStatRelation with scopes, HAS_MANY case ); } @@ -145,6 +150,26 @@ public function tableName() } } +/** + * @property integer $id + * @property string $first_name + * @property string $last_name + * @property string $country + * @property integer $user_id + */ +class Profile extends CActiveRecord +{ + public static function model($class=__CLASS__) + { + return parent::model($class); + } + + public function tableName() + { + return 'profiles'; + } +} + /** * @property integer $id * @property string $title @@ -190,6 +215,9 @@ public function scopes() 'post3'=>array('condition'=>'id=3'), 'postX'=>array('condition'=>'id=:id1 OR id=:id2', 'params'=>array(':id1'=>2, ':id2'=>3)), 'orderDesc'=>array('order'=>'posts.id DESC','alias'=>'posts'), + /* For {@link CActiveRecordTest::testRelationalStatWithScopes}: */ + 'recentScope'=>array('condition'=>"$this->tableAlias.create_time>=:create_time", 'params'=>array(':create_time'=>100002)),// CStatRelation with scopes, HAS_MANY case + 'recentScope2'=>array('condition'=>"$this->tableAlias.create_time>=:create_time", 'params'=>array(':create_time'=>100001)),// CStatRelation with scopes, MANY_MANY case ); } @@ -401,6 +429,9 @@ public function relations() 'children'=>array(self::HAS_MANY,'Category','parent_id'), 'nodes'=>array(self::HAS_MANY,'Category','parent_id','with'=>array('parent','children')), 'postCount'=>array(self::STAT, 'Post', 'post_category(post_id,category_id)'), + /* For {@link CActiveRecordTest::testRelationalStatWithScopes}: */ + 'recentPostCount1'=>array(self::STAT, 'Post', 'post_category(post_id,category_id)','scopes'=>'recentScope2'), // CStatRelation with scopes, MANY_MANY case + 'recentPostCount2'=>array(self::STAT, 'Post', 'post_category(post_id,category_id)','scopes'=>array('recentScope2')), // CStatRelation with scopes, MANY_MANY case ); } } @@ -958,6 +989,28 @@ public function relations() } } +class UserWithDefaultScopeAlias extends CActiveRecord +{ + public static function model($class=__CLASS__) + { + return parent::model($class); + } + + public function tableName() + { + return 'users'; + } + + public function defaultScope() + { + return array( + 'alias'=>'my_alias', + 'condition'=>"my_alias.username='user1'", + 'order'=>'my_alias.username', + ); + } +} + /** * @property integer $id * @property integer $from_id diff --git a/tests/framework/db/data/sqlite.sql b/tests/framework/db/data/sqlite.sql index 8fcf8b9490..cad4dab2e9 100644 --- a/tests/framework/db/data/sqlite.sql +++ b/tests/framework/db/data/sqlite.sql @@ -64,13 +64,14 @@ CREATE TABLE profiles id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, first_name VARCHAR(128) NOT NULL, last_name VARCHAR(128) NOT NULL, + country VARCHAR(128), user_id INTEGER NOT NULL, CONSTRAINT FK_profile_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT ); -INSERT INTO profiles (first_name, last_name, user_id) VALUES ('first 1','last 1',1); -INSERT INTO profiles (first_name, last_name, user_id) VALUES ('first 2','last 2',2); +INSERT INTO profiles (first_name, last_name, country, user_id) VALUES ('first 1','last 1','country 1',1); +INSERT INTO profiles (first_name, last_name, country, user_id) VALUES ('first 2','last 2','country 2',2); CREATE TABLE posts ( @@ -283,4 +284,4 @@ CREATE TABLE UserWithDefaultScopeLink INSERT INTO UserWithDefaultScopeLink (id,from_id,to_id) VALUES (1,1,2); INSERT INTO UserWithDefaultScopeLink (id,from_id,to_id) VALUES (2,2,3); -INSERT INTO UserWithDefaultScopeLink (id,from_id,to_id) VALUES (3,3,1); \ No newline at end of file +INSERT INTO UserWithDefaultScopeLink (id,from_id,to_id) VALUES (3,3,1); diff --git a/tests/framework/db/schema/CMysql2Test.php b/tests/framework/db/schema/CMysql2Test.php index 21deddc696..a848af4bc9 100644 --- a/tests/framework/db/schema/CMysql2Test.php +++ b/tests/framework/db/schema/CMysql2Test.php @@ -113,6 +113,14 @@ public function testAddForeignKey() $sql=$this->db->schema->addForeignKey('fk_test', 'profile', 'user_id', 'users', 'id','CASCADE','RESTRICTED'); $expect='ALTER TABLE `profile` ADD CONSTRAINT `fk_test` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICTED'; $this->assertEquals($expect, $sql); + + $sql=$this->db->schema->addForeignKey('fk_test', 'profile', 'user_id,id', 'users', 'id,username','CASCADE','RESTRICTED'); + $expect='ALTER TABLE `profile` ADD CONSTRAINT `fk_test` FOREIGN KEY (`user_id`, `id`) REFERENCES `users` (`id`, `username`) ON DELETE CASCADE ON UPDATE RESTRICTED'; + $this->assertEquals($expect, $sql); + + $sql=$this->db->schema->addForeignKey('fk_test', 'profile', array('user_id', 'id'), 'users', array('id','username'),'CASCADE','RESTRICTED'); + $expect='ALTER TABLE `profile` ADD CONSTRAINT `fk_test` FOREIGN KEY (`user_id`, `id`) REFERENCES `users` (`id`, `username`) ON DELETE CASCADE ON UPDATE RESTRICTED'; + $this->assertEquals($expect, $sql); } public function testDropForeignKey() @@ -131,6 +139,10 @@ public function testCreateIndex() $sql=$this->db->schema->createIndex('id_pk','test','id1,id2',true); $expect='CREATE UNIQUE INDEX `id_pk` ON `test` (`id1`, `id2`)'; $this->assertEquals($expect, $sql); + + $sql=$this->db->schema->createIndex('id_pk','test',array('id1','id2'),true); + $expect='CREATE UNIQUE INDEX `id_pk` ON `test` (`id1`, `id2`)'; + $this->assertEquals($expect, $sql); } public function testDropIndex() @@ -150,4 +162,4 @@ public function testAddPrimaryKey() $expect='ALTER TABLE `table` ADD PRIMARY KEY (`id1`, `id2` )'; $this->assertEquals($expect, $sql); } -} \ No newline at end of file +} diff --git a/tests/framework/db/schema/CPostgres2Test.php b/tests/framework/db/schema/CPostgres2Test.php index 0d515bc155..b3a360bb83 100644 --- a/tests/framework/db/schema/CPostgres2Test.php +++ b/tests/framework/db/schema/CPostgres2Test.php @@ -46,6 +46,27 @@ public function testCreateTable() $this->assertEquals($expect, $sql); } + public function testCreateTableBig() + { + $sql=$this->db->schema->createTable('test',array( + 'id'=>'bigpk', + 'name'=>'string not null', + 'desc'=>'text', + 'number'=>'bigint', + 'number2'=>'bigint not null default 0', + 'primary key (id, name)', + )); + $expect="CREATE TABLE \"test\" (\n" + . "\t\"id\" bigserial NOT NULL PRIMARY KEY,\n" + . "\t\"name\" character varying (255) not null,\n" + . "\t\"desc\" text,\n" + . "\t\"number\" bigint,\n" + . "\t\"number2\" bigint not null default 0,\n" + . "\tprimary key (id, name)\n" + . ")"; + $this->assertEquals($expect, $sql); + } + public function testRenameTable() { $sql=$this->db->schema->renameTable('test', 'test2'); @@ -97,6 +118,14 @@ public function testAddForeignKey() $sql=$this->db->schema->addForeignKey('fk_test', 'profile', 'user_id', 'users', 'id','CASCADE','RESTRICTED'); $expect='ALTER TABLE "profile" ADD CONSTRAINT "fk_test" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE RESTRICTED'; $this->assertEquals($expect, $sql); + + $sql=$this->db->schema->addForeignKey('fk_test', 'profile', 'user_id,id', 'users', 'id,username','CASCADE','RESTRICTED'); + $expect='ALTER TABLE "profile" ADD CONSTRAINT "fk_test" FOREIGN KEY ("user_id", "id") REFERENCES "users" ("id", "username") ON DELETE CASCADE ON UPDATE RESTRICTED'; + $this->assertEquals($expect, $sql); + + $sql=$this->db->schema->addForeignKey('fk_test', 'profile', array('user_id','id'), 'users', array('id','username'),'CASCADE','RESTRICTED'); + $expect='ALTER TABLE "profile" ADD CONSTRAINT "fk_test" FOREIGN KEY ("user_id", "id") REFERENCES "users" ("id", "username") ON DELETE CASCADE ON UPDATE RESTRICTED'; + $this->assertEquals($expect, $sql); } public function testDropForeignKey() @@ -113,6 +142,10 @@ public function testCreateIndex() $this->assertEquals($expect, $sql); $sql=$this->db->schema->createIndex('id_pk','test','id1,id2',true); + $expect='ALTER TABLE ONLY "test" ADD CONSTRAINT "id_pk" UNIQUE ("id1", "id2")'; + $this->assertEquals($expect, $sql); + + $sql=$this->db->schema->createIndex('id_pk','test',array('id1','id2'),true); $expect='CREATE UNIQUE INDEX "id_pk" ON "test" ("id1", "id2")'; $this->assertEquals($expect, $sql); } @@ -123,4 +156,4 @@ public function testDropIndex() $expect='DROP INDEX "id_pk"'; $this->assertEquals($expect, $sql); } -} \ No newline at end of file +} diff --git a/tests/framework/db/schema/CSqliteTest.php b/tests/framework/db/schema/CSqliteTest.php index cabf03fd9c..66d7ce0e98 100644 --- a/tests/framework/db/schema/CSqliteTest.php +++ b/tests/framework/db/schema/CSqliteTest.php @@ -333,4 +333,4 @@ public function testMultipleInsert() $this->assertTrue($expectedValue==$value,"Value for column '{$columnName}' incorrect!"); } } -} \ No newline at end of file +} diff --git a/tests/framework/i18n/CLocaleTest.php b/tests/framework/i18n/CLocaleTest.php index d4686dc029..a5fab00bad 100644 --- a/tests/framework/i18n/CLocaleTest.php +++ b/tests/framework/i18n/CLocaleTest.php @@ -139,8 +139,10 @@ public function providerGetLanguage() array('ru_RU','de','немецкий'), array('de','en_US','englisch'), array('de','en','englisch'), + array('de','US',null), array('de_DE','en_US','englisch'), array('de_DE','en','englisch'), + array('de_DE','US',null), array('es_MX',null,null), array('es_ES',null,null), @@ -151,13 +153,19 @@ public function providerGetLanguage() array('en_US','zh-Hant-HK','chinese'), array('ru','zh-Hant-HK','китайский'), array('en','zh-Hant-HK','chinese'), + array('ru','CN',null), + array('en','CN',null), + array('ru','Hant',null), + array('en','Hant',null), // https://github.com/yiisoft/yii/issues/2087 array('en_us','en','English'), array('en_us','en_us','English'), + array('en_us','us',null), array('en_us','pt','Portuguese'), array('en_us','pt','Portuguese'), array('en_us','pt_br','Portuguese'), + array('en_us','br','Breton'), array('en_us','pt_pt','Portuguese'), ); } @@ -184,8 +192,10 @@ public function providerGetScript() array('ru_RU','de',null), array('de','en_US',null), array('de','en',null), + array('de','US',null), array('de_DE','en_US',null), array('de_DE','en',null), + array('de_DE','US',null), array('es_MX',null,null), array('es_ES',null,null), @@ -196,6 +206,8 @@ public function providerGetScript() array('en_US','zh-Hant-HK','Traditional Han'), array('ru','zh-Hant-HK','Традиционный китайский'), array('en','zh-Hant-HK','Traditional Han'), + array('en','zh-CN',null), + array('en','zh-HK',null), ); } @@ -212,13 +224,13 @@ public function providerGetTerritory() { return array( array('en','fr_FR','France'), - array('en','fr',null), + array('en','fr','France'), array('en_US','fr_FR','France'), - array('en_US','fr',null), + array('en_US','fr','France'), array('ru','de_DE','Германия'), - array('ru','de',null), + array('ru','de','Германия'), array('ru_RU','de_DE','Германия'), - array('ru_RU','de',null), + array('ru_RU','de','Германия'), array('de','en_US','Vereinigte Staaten'), array('de','en',null), array('de_DE','en_US','Vereinigte Staaten'), @@ -229,10 +241,20 @@ public function providerGetTerritory() array('ru_RU','zh-Hans-CN','Китай'), array('en_US','zh-Hans-CN','China'), - array('ru_RU','zh-Hant-HK','Гонконг'), - array('en_US','zh-Hant-HK','Hong Kong'), - array('ru','zh-Hant-HK','Гонконг'), - array('en','zh-Hant-HK','Hong Kong'), + array('ru_RU','zh-CN','Китай'), + array('en_US','zh-CN','China'), + array('ru_RU','Hans-CN','Китай'), + array('en_US','Hans-CN','China'), + array('ru_RU','CN','Китай'), + array('en_US','CN','China'), + array('ru_RU','zh',null), + array('en_US','zh',null), + array('ru_RU','Hans',null), + array('en_US','Hans',null), + + array('fi_fi','se','Ruotsi'), + array('fi_fi','sv_se','Ruotsi'), + array('fi_fi','sv','El Salvador'), ); } diff --git a/tests/framework/utils/CFileHelperTest.php b/tests/framework/utils/CFileHelperTest.php index 2f20fe572e..14012d9da5 100644 --- a/tests/framework/utils/CFileHelperTest.php +++ b/tests/framework/utils/CFileHelperTest.php @@ -119,6 +119,39 @@ public function testRemoveDirectory() $this->assertFalse(is_file($bd.$this->rootDir1.$ds.$this->subDir.$ds.$this->file4)); } + public function testFindFiles_absolutePaths() + { + $this->createTestStruct($this->testDir); + + $bd=$this->testDir.DIRECTORY_SEPARATOR.$this->rootDir1.DIRECTORY_SEPARATOR; + + $files=CFileHelper::findFiles($this->testDir); + + $this->assertEquals($bd.'sub'.DIRECTORY_SEPARATOR.'..svn',$files[0]); + $this->assertEquals($bd.'sub'.DIRECTORY_SEPARATOR.'.htaccess',$files[1]); + $this->assertEquals($bd.'sub'.DIRECTORY_SEPARATOR.'testfile',$files[2]); + } + + public function testFindFiles_relativePaths() + { + $this->createTestStruct($this->testDir); + + $bd=$this->rootDir1.DIRECTORY_SEPARATOR; + + $files=CFileHelper::findFiles($this->testDir,array('absolutePaths'=>0)); + + $this->assertEquals($bd.'sub'.DIRECTORY_SEPARATOR.'..svn',$files[0]); + $this->assertEquals($bd.'sub'.DIRECTORY_SEPARATOR.'.htaccess',$files[1]); + $this->assertEquals($bd.'sub'.DIRECTORY_SEPARATOR.'testfile',$files[2]); + } + + public function testCreateDirectory() + { + $path = $this->testDir . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'path'; + $this->assertTrue(CFileHelper::createDirectory($path,null,true)); + $this->assertTrue(is_dir($path)); + } + private function createTestStruct($testDir) { $rootDir=$testDir.DIRECTORY_SEPARATOR.$this->rootDir1; diff --git a/tests/framework/validators/CFileValidatorTest.php b/tests/framework/validators/CFileValidatorTest.php index 197feb3a1a..aa84f5ab04 100644 --- a/tests/framework/validators/CFileValidatorTest.php +++ b/tests/framework/validators/CFileValidatorTest.php @@ -1,4 +1,5 @@ assertEquals($assertion, $fileValidator->sizeToBytes($sizeString)); } + + public function testValidate() + { + $model = new ValidatorTestModel(__CLASS__); + $uploadedFile = new CUploadedFile('test.txt', __FILE__, 'text/plain', 40, UPLOAD_ERR_OK); + $model->uploaded_file = $uploadedFile; + $this->assertTrue($model->validate(), 'Valid file validation failed!'); + } + + public function testValidateNoFile() + { + $model = new ValidatorTestModel(__CLASS__); + $uploadedFile = new CUploadedFile('test.txt', __FILE__, 'text/plain', 40, UPLOAD_ERR_NO_FILE); + $model->uploaded_file = $uploadedFile; + $this->assertFalse($model->validate(), 'File with error passed validation!'); + } } diff --git a/tests/framework/validators/CStringValidatorTest.php b/tests/framework/validators/CStringValidatorTest.php index 82122399b7..88345ecaf8 100644 --- a/tests/framework/validators/CStringValidatorTest.php +++ b/tests/framework/validators/CStringValidatorTest.php @@ -1,4 +1,5 @@ false, 'range' => array(0,1,7,13), 'on' => 'CRangeValidatorTest'), array('string2', 'in', 'allowEmpty' => false, 'range' => array('',1,7,13), 'on' => 'CRangeValidatorTest'), + + array('uploaded_file', 'file', 'on' => 'CFileValidatorTest'), ); } } diff --git a/tests/framework/web/CClientScriptTest.php b/tests/framework/web/CClientScriptTest.php index 0fd9b20a2f..bb9fe89b33 100644 --- a/tests/framework/web/CClientScriptTest.php +++ b/tests/framework/web/CClientScriptTest.php @@ -416,4 +416,66 @@ public function testRenderScripts($id, $script, $position, $htmlOptions, $assert $returnedClientScript->render($output); $this->assertContains($assertion, $output); } + + public function providerRenderScriptsBatch() + { + return array( + array( + array( + array( + 'id' => 'js_id_1', + 'script' => "function() {alert('script1')}", + 'position' => CClientScript::POS_HEAD, + 'htmlOptions' => array(), + ), + array( + 'id' => 'js_id_2', + 'script' => "function() {alert('script2')}", + 'position' => CClientScript::POS_HEAD, + 'htmlOptions' => array(), + ), + ), + 1 + ), + array( + array( + array( + 'id' => 'js_id_1', + 'script' => "function() {alert('script1')}", + 'position' => CClientScript::POS_HEAD, + 'htmlOptions' => array(), + ), + array( + 'id' => 'js_id_2', + 'script' => "function() {alert('script2')}", + 'position' => CClientScript::POS_HEAD, + 'htmlOptions' => array( + 'defer' => true + ), + ), + ), + 2 + ), + ); + } + + /** + * @depends testRenderScripts + * + * @dataProvider providerRenderScriptsBatch + * + * @param array $scriptBatch + * @param integer $expectedScriptTagCount + * + * @see https://github.com/yiisoft/yii/issues/2770 + */ + public function testRenderScriptsBatch(array $scriptBatch, $expectedScriptTagCount) + { + $this->_clientScript->reset(); + foreach($scriptBatch as $scriptParams) + $this->_clientScript->registerScript($scriptParams['id'], $scriptParams['script'], $scriptParams['position'], $scriptParams['htmlOptions']); + $output = ''; + $this->_clientScript->render($output); + $this->assertEquals($expectedScriptTagCount, substr_count($output, 'assertTrue(isset($directions['comments.id'])); } + + /** + * Tests for acceptance of arrays for asc/desc keys in + * CSort::attributes. + * + * @return void + */ + function testGetDirectionsWithArrays(){ + $_GET['sort'] = 'comments.id'; + + $criteria = new CDbCriteria(); + $criteria->with = 'comments'; + + $sort = new CSort('TestPost'); + $sort->attributes = array( + 'id', + 'comments.id' => array( + 'asc'=>array('comments.id', 'id'), + 'desc'=>array('comments.id desc', 'id desc'), + ), + ); + $sort->applyOrder($criteria); + $directions = $sort->getDirections(); + + $this->assertEquals($criteria->order, 'comments.id, id'); + } } @@ -81,4 +107,4 @@ public function relations() { 'post'=>array(self::BELONGS_TO, 'TestPost', 'post_id'), ); } -} \ No newline at end of file +} diff --git a/tests/framework/web/helpers/CJSONTest.php b/tests/framework/web/helpers/CJSONTest.php index eb341f845e..5b7cdd9149 100644 --- a/tests/framework/web/helpers/CJSONTest.php +++ b/tests/framework/web/helpers/CJSONTest.php @@ -76,4 +76,25 @@ public function testDecode(){ $this->assertEquals(array('a', 'b'), CJSON::decode('["a","b"]')); $this->assertEquals(array('a', 'b' => array('a', 'b' => 'c')), CJSON::decode('{"0":"a","b":{"0":"a","b":"c"}}')); } + + public function testJsonSerializable() + { + if(!interface_exists('JsonSerializable')) + $this->markTestSkipped('JsonSerializable interface is required.'); + + $className = get_class($this).'_JsonSerializable'; + $classCode = <<assertEquals(CJSON::encode($object), json_encode($object)); + } + + }